Browse Source

Implement some basic shader program types

pull/18/head
Ian Chamberlain 3 years ago
parent
commit
4492b2d293
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 55
      citro3d/examples/triangle.rs
  2. 165
      citro3d/src/shader.rs
  3. 24
      citro3d/src/shader/macros.rs

55
citro3d/examples/triangle.rs

@ -1,5 +1,5 @@
use citro3d::{include_aligned_bytes, shader};
use citro3d_sys::C3D_Mtx; use citro3d_sys::C3D_Mtx;
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::hid::{Hid, KeyPad}; use ctru::services::hid::{Hid, KeyPad};
@ -44,8 +44,8 @@ const VERTICES: &[Vertex] = &[
}, },
]; ];
static SHADER_BYTES: &[u8] = const SHADER_BYTES: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/examples/assets/vshader.shbin")); include_aligned_bytes!(concat!(env!("OUT_DIR"), "/examples/assets/vshader.shbin"));
fn main() { fn main() {
ctru::init(); ctru::init();
@ -72,7 +72,12 @@ fn main() {
render_target.set_output(&*top_screen, Side::Left); render_target.set_output(&*top_screen, Side::Left);
let (program, uloc_projection, projection, vbo_data, vshader_dvlb) = scene_init(); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap();
let vertex_shader = shader.get(0).unwrap();
let mut program = shader::Program::new(vertex_shader).unwrap();
let (uloc_projection, projection, vbo_data) = scene_init(&mut program);
while apt.main_loop() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();
@ -93,34 +98,18 @@ fn main() {
}); });
} }
scene_exit(vbo_data, program, vshader_dvlb); scene_exit(vbo_data);
} }
fn scene_init() -> (shaderProgram_s, i8, C3D_Mtx, *mut libc::c_void, *mut DVLB_s) { fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx, *mut libc::c_void) {
// Load the vertex shader, create a shader program and bind it // Load the vertex shader, create a shader program and bind it
unsafe { unsafe {
let mut shader_bytes = SHADER_BYTES.to_owned(); citro3d_sys::C3D_BindProgram(program.as_raw());
// Assume the data is aligned properly...
let vshader_dvlb = citro3d_sys::DVLB_ParseFile(
shader_bytes.as_mut_ptr().cast(),
(shader_bytes.len() / 4)
.try_into()
.expect("shader len fits in a u32"),
);
let mut program = {
let mut program = MaybeUninit::uninit();
citro3d_sys::shaderProgramInit(program.as_mut_ptr());
program.assume_init()
};
citro3d_sys::shaderProgramSetVsh(&mut program, (*vshader_dvlb).DVLE);
citro3d_sys::C3D_BindProgram(&mut program);
// Get the location of the uniforms // Get the location of the uniforms
let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap();
let uloc_projection = citro3d_sys::shaderInstanceGetUniformLocation( let uloc_projection = citro3d_sys::shaderInstanceGetUniformLocation(
program.vertexShader, (*program.as_raw()).vertexShader,
projection_name.as_ptr(), projection_name.as_ptr(),
); );
@ -183,13 +172,7 @@ fn scene_init() -> (shaderProgram_s, i8, C3D_Mtx, *mut libc::c_void, *mut DVLB_s
); );
citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, citro3d_sys::GPU_REPLACE); citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, citro3d_sys::GPU_REPLACE);
( (uloc_projection, projection, vbo_data.cast())
program,
uloc_projection,
projection,
vbo_data.cast(),
vshader_dvlb,
)
} }
} }
@ -210,16 +193,8 @@ fn scene_render(uloc_projection: i32, projection: &C3D_Mtx) {
} }
} }
fn scene_exit( fn scene_exit(vbo_data: *mut libc::c_void) {
vbo_data: *mut libc::c_void,
mut program: shaderProgram_s,
vshader_dvlb: *mut DVLB_s,
) {
unsafe { unsafe {
citro3d_sys::linearFree(vbo_data); citro3d_sys::linearFree(vbo_data);
citro3d_sys::shaderProgramFree(&mut program);
citro3d_sys::DVLB_Free(vshader_dvlb);
} }
} }

165
citro3d/src/shader.rs

@ -1 +1,166 @@
//! Functionality for parsing and using PICA200 shaders on the 3DS. This module
//! does not compile shaders, but enables using pre-compiled shaders at runtime.
//!
//! For more details about the PICA200 compiler / shader language, see
//! documentation for <https://github.com/devkitPro/picasso>.
use std::error::Error;
use std::mem::MaybeUninit;
pub mod macros;
/// A PICA200 shader program. It may have one or both of:
///
/// * A vertex [shader instance](Instance)
/// * A geometry [shader instance](Instance)
///
/// The PICA200 does not support user-programmable fragment shaders.
pub struct Program {
program: citro3d_sys::shaderProgram_s,
}
impl Program {
/// Create a new shader program from a vertex shader.
///
/// # Errors
///
/// Returns an error if:
/// * the shader program cannot be initialized
/// * the input shader is not a vertex shader or is otherwise invalid
pub fn new(vertex_shader: Entrypoint) -> Result<Self, ctru::Error> {
let mut program = unsafe {
let mut program = MaybeUninit::uninit();
let result = citro3d_sys::shaderProgramInit(program.as_mut_ptr());
if result != 0 {
return Err(ctru::Error::from(result));
}
program.assume_init()
};
let ret = unsafe { citro3d_sys::shaderProgramSetVsh(&mut program, vertex_shader.as_raw()) };
if ret == 0 {
Ok(Self { program })
} else {
Err(ctru::Error::from(ret))
}
}
/// Set the geometry shader for a given program.
///
/// # Errors
///
/// Returns an error if the input shader is not a geometry shader or is
/// otherwise invalid.
pub fn set_geometry_shader(
&mut self,
geometry_shader: Entrypoint,
stride: u8,
) -> Result<(), ctru::Error> {
let ret = unsafe {
citro3d_sys::shaderProgramSetGsh(&mut self.program, geometry_shader.as_raw(), stride)
};
if ret == 0 {
Ok(())
} else {
Err(ctru::Error::from(ret))
}
}
// TODO: pub(crate)
pub fn as_raw(&mut self) -> *mut citro3d_sys::shaderProgram_s {
&mut self.program
}
}
impl<'vert, 'geom> Drop for Program {
fn drop(&mut self) {
unsafe {
let _ = citro3d_sys::shaderProgramFree(self.as_raw());
}
}
}
/// A PICA200 Shader Library (commonly called DVLB). This can be comprised of
/// one or more [`Entrypoint`]s, but most commonly has one vertex shader and an
/// optional geometry shader.
///
/// This is the result of parsing a shader binary (shbin), and the resulting
/// [`Entrypoint`]s can be used as part of a [`Program`].
pub struct Library(*mut citro3d_sys::DVLB_s);
impl Library {
/// Parse a new shader library from input bytes.
///
/// # Errors
///
/// An error is returned if the input data does not have an alignment of 4
/// (cannot be safely converted to `&[u32]`).
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
unsafe {
let (prefix, aligned, suffix) = bytes.align_to::<u32>();
if !prefix.is_empty() || !suffix.is_empty() {
// Align is incorrect, we don't want to drop any data
// TODO fill in error details
return Err("uh oh".into());
}
Ok(Self(citro3d_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
// correct and it should just take a const arg in the API.
aligned.as_ptr() as *mut _,
aligned.len().try_into()?,
)))
}
}
#[must_use]
pub fn len(&self) -> usize {
unsafe { (*self.0).numDVLE as usize }
}
#[must_use]
pub fn get(&self, index: usize) -> Option<Entrypoint> {
if index < self.len() {
Some(Entrypoint {
ptr: unsafe { (*self.0).DVLE.add(index) },
_library: self,
})
} else {
None
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn as_raw(&mut self) -> *mut citro3d_sys::DVLB_s {
self.0
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe {
citro3d_sys::DVLB_Free(self.as_raw());
}
}
}
/// A shader library entrypoint (also called DVLE). This represents either a
/// vertex or a geometry shader.
#[derive(Clone, Copy)]
pub struct Entrypoint<'lib> {
ptr: *mut citro3d_sys::DVLE_s,
_library: &'lib Library,
}
impl<'lib> Entrypoint<'lib> {
fn as_raw(self) -> *mut citro3d_sys::DVLE_s {
self.ptr
}
}

24
citro3d/src/shader/macros.rs

@ -0,0 +1,24 @@
/// Helper struct to [`include_bytes`] aligned as a specific type.
#[repr(C)] // guarantee 'bytes' comes after '_align'
pub struct AlignedAs<Align, Bytes: ?Sized> {
pub _align: [Align; 0],
pub bytes: Bytes,
}
/// Helper macro for including a file as bytes that are correctly aligned for
/// use as a [`Library`](super::Library).
#[macro_export]
macro_rules! include_aligned_bytes {
($path:expr) => {{
// const block expression to encapsulate the static
use $crate::shader::macros::AlignedAs;
// this assignment is made possible by CoerceUnsized
const ALIGNED: &AlignedAs<u32, [u8]> = &AlignedAs {
_align: [],
bytes: *include_bytes!($path),
};
&ALIGNED.bytes
}};
}
Loading…
Cancel
Save