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::borrow::Cow;
8use std::error::Error;
9use std::ffi::CString;
10use std::mem::MaybeUninit;
11use std::rc::Rc;
12
13use crate::uniform;
14
15/// A PICA200 shader program. It may have one or both of:
16///
17/// * A [vertex](Type::Vertex) shader [`Library`]
18/// * A [geometry](Type::Geometry) shader [`Library`]
19///
20/// The PICA200 does not support user-programmable fragment shaders.
21#[doc(alias = "shaderProgram_s")]
22#[must_use]
23pub struct Program {
24    program: ctru_sys::shaderProgram_s,
25    _vsh: Entrypoint,
26    gsh: Option<Entrypoint>,
27}
28
29impl Program {
30    /// Create a new shader program from a vertex shader.
31    ///
32    /// # Errors
33    ///
34    /// Returns an error if:
35    /// * the shader program cannot be initialized
36    /// * the input shader is not a vertex shader or is otherwise invalid
37    #[doc(alias = "shaderProgramInit")]
38    #[doc(alias = "shaderProgramSetVsh")]
39    pub fn new(mut vertex_shader: Entrypoint) -> Result<Self, ctru::Error> {
40        let mut program = unsafe {
41            let mut program = MaybeUninit::uninit();
42            let result = ctru_sys::shaderProgramInit(program.as_mut_ptr());
43            if result != 0 {
44                return Err(ctru::Error::from(result));
45            }
46            program.assume_init()
47        };
48
49        let ret = unsafe { ctru_sys::shaderProgramSetVsh(&mut program, vertex_shader.as_raw()) };
50
51        if ret == 0 {
52            Ok(Self {
53                program,
54                _vsh: vertex_shader,
55                gsh: None,
56            })
57        } else {
58            Err(ctru::Error::from(ret))
59        }
60    }
61
62    /// Set the geometry shader for a given program.
63    ///
64    /// # Errors
65    ///
66    /// Returns an error if the input shader is not a geometry shader or is
67    /// otherwise invalid.
68    #[doc(alias = "shaderProgramSetGsh")]
69    pub fn set_geometry_shader(
70        &mut self,
71        mut geometry_shader: Entrypoint,
72        stride: u8,
73    ) -> Result<(), ctru::Error> {
74        let ret = unsafe {
75            ctru_sys::shaderProgramSetGsh(&mut self.program, geometry_shader.as_raw(), stride)
76        };
77
78        if ret == 0 {
79            self.gsh = Some(geometry_shader);
80            Ok(())
81        } else {
82            Err(ctru::Error::from(ret))
83        }
84    }
85
86    /// Get the index of a uniform in the vertex shader by name.
87    ///
88    /// # Errors
89    ///
90    /// * If the given `name` contains a null byte
91    /// * If a uniform with the given `name` could not be found
92    #[doc(alias = "shaderInstanceGetUniformLocation")]
93    pub fn get_vertex_uniform(&self, name: &str) -> crate::Result<uniform::Index> {
94        let vertex_instance = unsafe { (*self.as_raw()).vertexShader };
95        assert!(
96            !vertex_instance.is_null(),
97            "vertex shader should never be null!"
98        );
99
100        let name = CString::new(name)?;
101
102        let idx =
103            unsafe { ctru_sys::shaderInstanceGetUniformLocation(vertex_instance, name.as_ptr()) };
104
105        if idx < 0 {
106            Err(crate::Error::NotFound)
107        } else {
108            Ok((idx as u8).into())
109        }
110    }
111
112    /// Get the index of a uniform in the geometry shader by name.
113    ///
114    /// # Errors
115    ///
116    /// * If a geometry shader has not been set
117    /// * If the given `name` contains a null byte
118    /// * If a uniform with the given `name` could not be found
119    #[doc(alias = "shaderInstanceGetUniformLocation")]
120    pub fn get_geometry_uniform(&self, name: &str) -> crate::Result<uniform::Index> {
121        if self.gsh.is_none() {
122            return Err(crate::Error::MissingProgram);
123        }
124
125        let geometry_instance = unsafe { (*self.as_raw()).geometryShader };
126
127        let name = CString::new(name)?;
128
129        let idx =
130            unsafe { ctru_sys::shaderInstanceGetUniformLocation(geometry_instance, name.as_ptr()) };
131
132        if idx < 0 {
133            Err(crate::Error::NotFound)
134        } else {
135            Ok((idx as u8).into())
136        }
137    }
138
139    pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s {
140        &self.program
141    }
142}
143
144impl Drop for Program {
145    #[doc(alias = "shaderProgramFree")]
146    fn drop(&mut self) {
147        unsafe {
148            let _ = ctru_sys::shaderProgramFree(self.as_raw().cast_mut());
149        }
150    }
151}
152
153/// The type of a shader.
154#[repr(u8)]
155#[derive(Clone, Copy)]
156pub enum Type {
157    /// A vertex shader.
158    Vertex = ctru_sys::GPU_VERTEX_SHADER,
159    /// A geometry shader.
160    Geometry = ctru_sys::GPU_GEOMETRY_SHADER,
161}
162
163impl From<Type> for u8 {
164    fn from(value: Type) -> Self {
165        value as u8
166    }
167}
168
169/// A PICA200 Shader Library (commonly called DVLB). This can be comprised of
170/// one or more [`Entrypoint`]s, but most commonly has one vertex shader and an
171/// optional geometry shader.
172///
173/// This is the result of parsing a shader binary (`.shbin`), and the resulting
174/// [`Entrypoint`]s can be used as part of a [`Program`].
175#[doc(alias = "DVLB_s")]
176pub struct Library {
177    dvlb: *mut ctru_sys::DVLB_s,
178    _bytes: Cow<'static, [u8]>,
179}
180
181impl Library {
182    /// Parse a new shader library from input bytes.
183    ///
184    /// # Errors
185    ///
186    /// An error is returned if the input data does not have an alignment of 4
187    /// (cannot be safely converted to `&[u32]`).
188    #[doc(alias = "DVLB_ParseFile")]
189    pub fn from_bytes<B: Into<Cow<'static, [u8]>>>(bytes: B) -> Result<Self, Box<dyn Error>> {
190        let bytes = bytes.into();
191
192        let aligned: &[u32] = bytemuck::try_cast_slice(&bytes)?;
193        let dvlb = unsafe {
194            ctru_sys::DVLB_ParseFile(
195                // SAFETY: we're trusting the parse implementation doesn't mutate
196                // the contents of the data. From a quick read it looks like that's
197                // correct and it should just take a const arg in the API.
198                aligned.as_ptr().cast_mut(),
199                aligned.len().try_into()?,
200            )
201        };
202
203        Ok(Self {
204            dvlb,
205            _bytes: bytes,
206        })
207    }
208
209    /// Get the number of [`Entrypoint`]s in this shader library.
210    #[must_use]
211    #[doc(alias = "numDVLE")]
212    pub fn len(&self) -> usize {
213        unsafe { (*self.dvlb).numDVLE as usize }
214    }
215
216    /// Whether the library has any [`Entrypoint`]s or not.
217    #[must_use]
218    pub fn is_empty(&self) -> bool {
219        self.len() == 0
220    }
221
222    /// Get the [`Entrypoint`] at the given index, if present.
223    #[must_use]
224    pub fn get(self, index: usize) -> Option<Entrypoint> {
225        if index < self.len() {
226            Some(Entrypoint {
227                ptr: unsafe { (*self.dvlb).DVLE.add(index) },
228                _library: MaybeRc::Owned(self),
229            })
230        } else {
231            None
232        }
233    }
234
235    #[must_use]
236    /// Get the [`Entrypoint`] at the given index, if present.
237    ///
238    /// Like [`Library::get`], except takes `Rc<Self>` instead of `Self`, to allow the same library
239    /// to have multiple entrypoints
240    pub fn get_shared(self: Rc<Self>, index: usize) -> Option<Entrypoint> {
241        if index < self.len() {
242            Some(Entrypoint {
243                ptr: unsafe { (*self.dvlb).DVLE.add(index) },
244                _library: MaybeRc::Shared(self),
245            })
246        } else {
247            None
248        }
249    }
250
251    fn as_raw(&mut self) -> *mut ctru_sys::DVLB_s {
252        self.dvlb
253    }
254}
255
256impl Drop for Library {
257    #[doc(alias = "DVLB_Free")]
258    fn drop(&mut self) {
259        unsafe {
260            ctru_sys::DVLB_Free(self.as_raw());
261        }
262    }
263}
264
265#[allow(dead_code)]
266enum MaybeRc<T> {
267    Owned(T),
268    Shared(Rc<T>),
269}
270
271/// A shader library entrypoint (also called DVLE). This represents either a
272/// vertex or a geometry shader.
273pub struct Entrypoint {
274    ptr: *mut ctru_sys::DVLE_s,
275    _library: MaybeRc<Library>,
276}
277
278impl Entrypoint {
279    fn as_raw(&mut self) -> *mut ctru_sys::DVLE_s {
280        self.ptr
281    }
282}