1use std::mem::MaybeUninit;
2
3use citro3d_sys::C3D_TexCalcMaxLevel;
4pub use enums::*;
5
6mod enums;
7
8pub const TEXTURE_COUNT: usize = 4;
10pub const MIN_TEX_SIZE: u16 = 8;
12pub const MAX_TEX_SIZE: u16 = 1024;
14
15#[derive(Debug, Clone)]
17pub struct TextureParameters {
18 pub width: u16,
19 pub height: u16,
20 pub max_level: u8,
21 pub format: ColorFormat,
22 pub mode: Mode,
23 pub on_vram: bool,
24}
25
26impl TextureParameters {
29 pub const fn new_2d(width: u16, height: u16, format: ColorFormat) -> TextureParameters {
31 TextureParameters {
32 width,
33 height,
34 max_level: 0,
35 format,
36 mode: Mode::Tex2D,
37 on_vram: false,
38 }
39 }
40
41 pub fn new_2d_with_mipmap(width: u16, height: u16, format: ColorFormat) -> TextureParameters {
43 TextureParameters {
44 width,
45 height,
46 max_level: unsafe { C3D_TexCalcMaxLevel(width as u32, height as u32) as u8 },
47 format,
48 mode: Mode::Tex2D,
49 on_vram: false,
50 }
51 }
52
53 pub const fn new_2d_in_vram(width: u16, height: u16, format: ColorFormat) -> TextureParameters {
55 TextureParameters {
56 width,
57 height,
58 max_level: 0,
59 format,
60 mode: Mode::Tex2D,
61 on_vram: true,
62 }
63 }
64
65 pub const fn new_shadow(width: u16, height: u16) -> TextureParameters {
67 TextureParameters {
68 width,
69 height,
70 max_level: 0,
71 format: ColorFormat::Rgba8,
72 mode: Mode::Shadow2D,
73 on_vram: true,
74 }
75 }
76}
77
78impl From<TextureParameters> for citro3d_sys::C3D_TexInitParams {
79 fn from(value: TextureParameters) -> Self {
80 citro3d_sys::C3D_TexInitParams {
81 width: value.width,
82 height: value.height,
83 _bitfield_align_1: [],
84 _bitfield_1: citro3d_sys::C3D_TexInitParams::new_bitfield_1(
85 value.max_level,
86 value.format as u8,
87 value.mode as u8,
88 value.on_vram,
89 ),
90 __bindgen_padding_0: 0,
91 }
92 }
93}
94
95#[allow(non_camel_case_types, non_snake_case)]
97#[repr(C)]
98struct Tex3DS_Texture_s {
99 numSubTextures: u16,
100 width: u16,
101 height: u16,
102 format: u8,
103 mipmapLevels: u8,
104 subTextures: [citro3d_sys::Tex3DS_SubTexture; 1],
105}
106
107pub struct Tex3DSTexture {
108 texture: Texture,
109 tex3ds: Box<Tex3DS_Texture_s>,
110}
111
112impl Tex3DSTexture {
113 pub fn texture(&self) -> &Texture {
114 &self.texture
115 }
116
117 pub fn into_texture(self) -> Texture {
118 self.texture
119 }
120
121 pub fn sub_textures(&self) -> &[citro3d_sys::Tex3DS_SubTexture] {
122 unsafe {
126 std::slice::from_raw_parts(
127 self.tex3ds.subTextures.as_ptr(),
128 self.tex3ds.numSubTextures as usize,
129 )
130 }
131 }
132
133 #[doc(alias = "Tex3DS_TextureImport")]
135 pub fn new(data: &[u8], use_vram: bool) -> Option<Tex3DSTexture> {
136 let mut texture: Texture = unsafe { std::mem::zeroed() };
137 let mut cube: Box<citro3d_sys::C3D_TexCube> = unsafe { Box::new(std::mem::zeroed()) };
138
139 let t3d: *mut Tex3DS_Texture_s = unsafe {
141 citro3d_sys::Tex3DS_TextureImport(
142 data.as_ptr() as _,
143 data.len(),
144 texture.as_raw(),
145 cube.as_mut(),
146 use_vram,
147 ) as _
148 };
149
150 if t3d.is_null() {
151 return None;
152 }
153
154 let tex3ds = unsafe { Box::from_raw(t3d) };
155
156 texture.format = tex3ds.format.try_into().ok()?;
157 texture.cube = Some(cube);
160
161 Some(Tex3DSTexture { texture, tex3ds })
162 }
163}
164
165pub struct Texture {
166 pub(crate) tex: citro3d_sys::C3D_Tex,
167 pub(crate) format: ColorFormat,
168 pub(crate) in_vram: bool,
169 pub(crate) cube: Option<Box<citro3d_sys::C3D_TexCube>>,
172}
173
174impl Texture {
175 #[doc(alias = "C3D_TexInit")]
179 pub fn new(params: TextureParameters) -> crate::error::Result<Self> {
180 if !check_texture_size(params.width) || !check_texture_size(params.height) {
181 return Err(crate::Error::InvalidSize);
182 }
183
184 let mut cube: Option<Box<citro3d_sys::C3D_TexCube>> = None;
185 if params.mode == Mode::CubeMap || params.mode == Mode::ShadowCube {
186 cube = unsafe { Some(Box::new(std::mem::zeroed())) };
187 }
188
189 let format = params.format;
190 let in_vram = params.on_vram;
191 let params: citro3d_sys::C3D_TexInitParams = params.into();
192
193 unsafe {
196 let mut c3d_tex: MaybeUninit<citro3d_sys::C3D_Tex> = core::mem::zeroed();
197
198 let success = citro3d_sys::C3D_TexInitWithParams(
199 c3d_tex.as_mut_ptr(),
200 cube.as_mut().map(|p| p.as_mut() as _).unwrap_or_default(),
201 params,
202 );
203
204 if !success {
205 return Err(crate::Error::FailedToInitialize);
206 }
207
208 let mut tex = Texture {
209 tex: c3d_tex.assume_init(),
210 format,
211 in_vram,
212 cube,
213 };
214
215 tex.set_filter(Filter::Linear, Filter::Nearest);
217
218 Ok(tex)
219 }
220 }
221
222 #[doc(alias = "C3D_TexUpload")]
226 pub fn load_image(&mut self, data: &[u8], face: Face) -> crate::Result<()> {
227 self.load_image_at_mipmap_level(data, face, 0)
228 }
229
230 #[doc(alias = "C3D_TexLoadImage")]
234 pub fn load_image_at_mipmap_level(
235 &mut self,
236 data: &[u8],
237 face: Face,
238 mipmap_level: u8,
239 ) -> crate::Result<()> {
240 let size = unsafe {
241 if mipmap_level > 0 {
242 citro3d_sys::C3D_TexCalcLevelSize(
243 self.format.bits_per_pixel() as u32,
244 mipmap_level as i32,
245 )
246 } else {
247 citro3d_sys::C3D_TexCalcTotalSize(
248 self.format.bits_per_pixel() as u32,
249 self.max_level() as i32,
250 )
251 }
252 };
253
254 if data.len() < size as usize {
256 return Err(crate::Error::InvalidSize);
257 }
258
259 unsafe {
261 citro3d_sys::C3D_TexLoadImage(
262 self.as_raw(),
263 data.as_ptr() as *const _,
264 face as u8,
265 mipmap_level as i32,
266 );
267 }
268
269 Ok(())
270 }
271
272 pub(crate) unsafe fn bind(&self, index: Index) {
276 unsafe { citro3d_sys::C3D_TexBind(index as _, &self.tex as *const _ as *mut _) };
277 }
278
279 pub fn generate_mipmap(&mut self, face: Face) {
282 unsafe {
283 citro3d_sys::C3D_TexGenerateMipmap(&mut self.tex as *mut _, face as u8);
284 }
285 }
286
287 pub fn set_filter(&mut self, mag_filter: Filter, min_filter: Filter) {
288 unsafe { citro3d_sys::C3D_TexSetFilter(self.as_raw(), mag_filter as u8, min_filter as u8) };
289 }
290
291 pub fn set_filter_mipmap(&mut self, filter: Filter) {
292 unsafe {
293 citro3d_sys::C3D_TexSetFilterMipmap(self.as_raw(), filter as u8);
294 }
295 }
296
297 pub fn set_wrap(&mut self, wrap_s: Wrap, wrap_t: Wrap) {
298 unsafe {
299 citro3d_sys::C3D_TexSetWrap(self.as_raw(), wrap_s as u8, wrap_t as u8);
300 }
301 }
302
303 pub fn set_lod_bias(&mut self, lod_bias: f32) {
304 unsafe {
305 citro3d_sys::C3D_TexSetLodBias(self.as_raw(), lod_bias);
306 }
307 }
308
309 pub fn width(&self) -> u16 {
310 unsafe { self.tex.__bindgen_anon_2.__bindgen_anon_1.width }
311 }
312
313 pub fn height(&self) -> u16 {
314 unsafe { self.tex.__bindgen_anon_2.__bindgen_anon_1.height }
315 }
316
317 pub fn param(&self) -> u32 {
318 self.tex.param
319 }
320
321 pub fn format(&self) -> ColorFormat {
322 self.format
323 }
324
325 pub fn lod_bias(&self) -> u16 {
326 unsafe { self.tex.__bindgen_anon_3.__bindgen_anon_1.lodBias }
327 }
328
329 pub fn max_level(&self) -> u8 {
330 unsafe { self.tex.__bindgen_anon_3.__bindgen_anon_1.maxLevel }
331 }
332
333 pub fn min_level(&self) -> u8 {
334 unsafe { self.tex.__bindgen_anon_3.__bindgen_anon_1.minLevel }
335 }
336
337 fn as_raw(&self) -> *mut citro3d_sys::C3D_Tex {
338 &self.tex as *const _ as *mut _
339 }
340}
341
342impl Drop for Texture {
343 fn drop(&mut self) {
344 unsafe { citro3d_sys::C3D_TexDelete(self.as_raw()) }
346 }
347}
348
349fn check_texture_size(size: u16) -> bool {
350 if !(MIN_TEX_SIZE..=MAX_TEX_SIZE).contains(&size) {
351 return false;
352 }
353
354 if (size & (size - 1)) > 0 {
355 return false;
356 }
357
358 true
359}