citro3d/
shader.rs

1//! Functionality for parsing and using PICA200 shaders on the 3DS. This module
2//! does not compile shaders, but enables using pre-compiled shaders at runtime.
3//!
4//! For more details about the PICA200 compiler / shader language, see
5//! documentation for <https://github.com/devkitPro/picasso>.
6
7use std::error::Error;
8use std::ffi::CString;
9use std::mem::MaybeUninit;
10
11use crate::uniform;
12
13/// A PICA200 shader program. It may have one or both of:
14///
15/// * A [vertex](Type::Vertex) shader [`Library`]
16/// * A [geometry](Type::Geometry) shader [`Library`]
17///
18/// The PICA200 does not support user-programmable fragment shaders.
19#[doc(alias = "shaderProgram_s")]
20#[must_use]
21pub struct Program {
22    program: ctru_sys::shaderProgram_s,
23}
24
25impl Program {
26    /// Create a new shader program from a vertex shader.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if:
31    /// * the shader program cannot be initialized
32    /// * the input shader is not a vertex shader or is otherwise invalid
33    #[doc(alias = "shaderProgramInit")]
34    #[doc(alias = "shaderProgramSetVsh")]
35    pub fn new(vertex_shader: Entrypoint) -> Result<Self, ctru::Error> {
36        let mut program = unsafe {
37            let mut program = MaybeUninit::uninit();
38            let result = ctru_sys::shaderProgramInit(program.as_mut_ptr());
39            if result != 0 {
40                return Err(ctru::Error::from(result));
41            }
42            program.assume_init()
43        };
44
45        let ret = unsafe { ctru_sys::shaderProgramSetVsh(&mut program, vertex_shader.as_raw()) };
46
47        if ret == 0 {
48            Ok(Self { program })
49        } else {
50            Err(ctru::Error::from(ret))
51        }
52    }
53
54    /// Set the geometry shader for a given program.
55    ///
56    /// # Errors
57    ///
58    /// Returns an error if the input shader is not a geometry shader or is
59    /// otherwise invalid.
60    #[doc(alias = "shaderProgramSetGsh")]
61    pub fn set_geometry_shader(
62        &mut self,
63        geometry_shader: Entrypoint,
64        stride: u8,
65    ) -> Result<(), ctru::Error> {
66        let ret = unsafe {
67            ctru_sys::shaderProgramSetGsh(&mut self.program, geometry_shader.as_raw(), stride)
68        };
69
70        if ret == 0 {
71            Ok(())
72        } else {
73            Err(ctru::Error::from(ret))
74        }
75    }
76
77    /// Get the index of a uniform by name.
78    ///
79    /// # Errors
80    ///
81    /// * If the given `name` contains a null byte
82    /// * If a uniform with the given `name` could not be found
83    #[doc(alias = "shaderInstanceGetUniformLocation")]
84    pub fn get_uniform(&self, name: &str) -> crate::Result<uniform::Index> {
85        let vertex_instance = unsafe { (*self.as_raw()).vertexShader };
86        assert!(
87            !vertex_instance.is_null(),
88            "vertex shader should never be null!"
89        );
90
91        let name = CString::new(name)?;
92
93        let idx =
94            unsafe { ctru_sys::shaderInstanceGetUniformLocation(vertex_instance, name.as_ptr()) };
95
96        if idx < 0 {
97            Err(crate::Error::NotFound)
98        } else {
99            Ok((idx as u8).into())
100        }
101    }
102
103    pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s {
104        &self.program
105    }
106}
107
108impl Drop for Program {
109    #[doc(alias = "shaderProgramFree")]
110    fn drop(&mut self) {
111        unsafe {
112            let _ = ctru_sys::shaderProgramFree(self.as_raw().cast_mut());
113        }
114    }
115}
116
117/// The type of a shader.
118#[repr(u8)]
119#[derive(Clone, Copy)]
120pub enum Type {
121    /// A vertex shader.
122    Vertex = ctru_sys::GPU_VERTEX_SHADER,
123    /// A geometry shader.
124    Geometry = ctru_sys::GPU_GEOMETRY_SHADER,
125}
126
127impl From<Type> for u8 {
128    fn from(value: Type) -> Self {
129        value as u8
130    }
131}
132
133/// A PICA200 Shader Library (commonly called DVLB). This can be comprised of
134/// one or more [`Entrypoint`]s, but most commonly has one vertex shader and an
135/// optional geometry shader.
136///
137/// This is the result of parsing a shader binary (`.shbin`), and the resulting
138/// [`Entrypoint`]s can be used as part of a [`Program`].
139#[doc(alias = "DVLB_s")]
140pub struct Library(*mut ctru_sys::DVLB_s);
141
142impl Library {
143    /// Parse a new shader library from input bytes.
144    ///
145    /// # Errors
146    ///
147    /// An error is returned if the input data does not have an alignment of 4
148    /// (cannot be safely converted to `&[u32]`).
149    #[doc(alias = "DVLB_ParseFile")]
150    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
151        let aligned: &[u32] = bytemuck::try_cast_slice(bytes)?;
152        Ok(Self(unsafe {
153            ctru_sys::DVLB_ParseFile(
154                // SAFETY: we're trusting the parse implementation doesn't mutate
155                // the contents of the data. From a quick read it looks like that's
156                // correct and it should just take a const arg in the API.
157                aligned.as_ptr().cast_mut(),
158                aligned.len().try_into()?,
159            )
160        }))
161    }
162
163    /// Get the number of [`Entrypoint`]s in this shader library.
164    #[must_use]
165    #[doc(alias = "numDVLE")]
166    pub fn len(&self) -> usize {
167        unsafe { (*self.0).numDVLE as usize }
168    }
169
170    /// Whether the library has any [`Entrypoint`]s or not.
171    #[must_use]
172    pub fn is_empty(&self) -> bool {
173        self.len() == 0
174    }
175
176    /// Get the [`Entrypoint`] at the given index, if present.
177    #[must_use]
178    pub fn get(&self, index: usize) -> Option<Entrypoint<'_>> {
179        if index < self.len() {
180            Some(Entrypoint {
181                ptr: unsafe { (*self.0).DVLE.add(index) },
182                _library: self,
183            })
184        } else {
185            None
186        }
187    }
188
189    fn as_raw(&mut self) -> *mut ctru_sys::DVLB_s {
190        self.0
191    }
192}
193
194impl Drop for Library {
195    #[doc(alias = "DVLB_Free")]
196    fn drop(&mut self) {
197        unsafe {
198            ctru_sys::DVLB_Free(self.as_raw());
199        }
200    }
201}
202
203/// A shader library entrypoint (also called DVLE). This represents either a
204/// vertex or a geometry shader.
205#[derive(Clone, Copy)]
206pub struct Entrypoint<'lib> {
207    ptr: *mut ctru_sys::DVLE_s,
208    _library: &'lib Library,
209}
210
211impl<'lib> Entrypoint<'lib> {
212    fn as_raw(self) -> *mut ctru_sys::DVLE_s {
213        self.ptr
214    }
215}