Skip to main content

libretro_core/
camera.rs

1//! Camera capability, request, frame, and service-interface wrappers.
2//!
3//! Camera callbacks are event-shaped: cores register handlers through
4//! `CoreEventConfig`, while camera start/stop capability is exposed through the
5//! typed `CameraInterface` service.
6
7use enumflags2::{BitFlags, bitflags};
8
9use crate::raw;
10
11#[bitflags]
12#[repr(u64)]
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum CameraCapability {
15    OpenGlTexture = 1u64 << (raw::retro_camera_buffer::OpenGlTexture as u64),
16    RawFramebuffer = 1u64 << (raw::retro_camera_buffer::RawFramebuffer as u64),
17}
18
19pub type CameraCapabilities = BitFlags<CameraCapability>;
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
22pub struct CameraFrameSize {
23    pub width: u32,
24    pub height: u32,
25}
26
27impl CameraFrameSize {
28    pub const fn new(width: u32, height: u32) -> Self {
29        Self { width, height }
30    }
31
32    pub const fn frontend_default() -> Self {
33        Self {
34            width: 0,
35            height: 0,
36        }
37    }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub struct CameraRequest {
42    pub capabilities: CameraCapabilities,
43    pub size: CameraFrameSize,
44}
45
46impl CameraRequest {
47    pub fn raw_framebuffer() -> Self {
48        Self {
49            capabilities: CameraCapabilities::from(CameraCapability::RawFramebuffer),
50            size: CameraFrameSize::frontend_default(),
51        }
52    }
53
54    pub fn open_gl_texture() -> Self {
55        Self {
56            capabilities: CameraCapabilities::from(CameraCapability::OpenGlTexture),
57            size: CameraFrameSize::frontend_default(),
58        }
59    }
60
61    pub fn with_size(mut self, size: CameraFrameSize) -> Self {
62        self.size = size;
63        self
64    }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct CameraRawFrame<'a> {
69    pub pixels: &'a [u32],
70    pub width: u32,
71    pub height: u32,
72    pub pitch_bytes: usize,
73}
74
75impl<'a> CameraRawFrame<'a> {
76    pub(crate) unsafe fn from_raw(
77        buffer: *const u32,
78        width: u32,
79        height: u32,
80        pitch_bytes: usize,
81    ) -> Option<Self> {
82        if buffer.is_null() {
83            return None;
84        }
85        let words_per_row = pitch_bytes.checked_div(std::mem::size_of::<u32>())?;
86        let len = words_per_row.checked_mul(height as usize)?;
87        Some(Self {
88            pixels: unsafe { std::slice::from_raw_parts(buffer, len) },
89            width,
90            height,
91            pitch_bytes,
92        })
93    }
94}
95
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
97pub struct CameraTextureId(u32);
98
99impl CameraTextureId {
100    pub const fn new(id: u32) -> Self {
101        Self(id)
102    }
103
104    pub const fn get(self) -> u32 {
105        self.0
106    }
107}
108
109#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
110pub struct CameraTextureTarget(u32);
111
112impl CameraTextureTarget {
113    pub const fn new(target: u32) -> Self {
114        Self(target)
115    }
116
117    pub const fn get(self) -> u32 {
118        self.0
119    }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq)]
123pub struct CameraTextureFrame {
124    pub texture_id: CameraTextureId,
125    pub texture_target: CameraTextureTarget,
126    pub affine: [f32; 9],
127}
128
129#[derive(Clone, Copy, Debug, Default)]
130pub struct CameraInterface {
131    raw: raw::retro_camera_callback,
132}
133
134impl CameraInterface {
135    pub(crate) const fn from_raw(raw: raw::retro_camera_callback) -> Self {
136        Self { raw }
137    }
138
139    pub const fn is_available(self) -> bool {
140        self.raw.start.is_some() && self.raw.stop.is_some()
141    }
142
143    pub fn start(self) -> bool {
144        self.raw.start.is_some_and(|start| unsafe { start() })
145    }
146
147    pub fn stop(self) -> bool {
148        let Some(stop) = self.raw.stop else {
149            return false;
150        };
151        unsafe { stop() };
152        true
153    }
154
155    pub fn capabilities(self) -> CameraCapabilities {
156        CameraCapabilities::from_bits_truncate(self.raw.caps)
157    }
158
159    pub const fn size(self) -> CameraFrameSize {
160        CameraFrameSize {
161            width: self.raw.width,
162            height: self.raw.height,
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::{
170        CameraCapabilities, CameraCapability, CameraFrameSize, CameraInterface, CameraRequest,
171        CameraTextureId, CameraTextureTarget,
172    };
173
174    #[test]
175    fn camera_capabilities_encode_libretro_bits() {
176        let capabilities = CameraCapabilities::from(CameraCapability::RawFramebuffer)
177            | CameraCapability::OpenGlTexture;
178
179        assert!(capabilities.contains(CameraCapability::RawFramebuffer));
180        assert!(capabilities.contains(CameraCapability::OpenGlTexture));
181        assert_eq!(capabilities.bits(), 0b11);
182    }
183
184    #[test]
185    fn camera_request_preserves_size_hints() {
186        let request = CameraRequest::raw_framebuffer().with_size(CameraFrameSize::new(320, 240));
187
188        assert_eq!(request.size, CameraFrameSize::new(320, 240));
189        assert!(
190            request
191                .capabilities
192                .contains(CameraCapability::RawFramebuffer)
193        );
194        assert!(
195            CameraRequest::open_gl_texture()
196                .capabilities
197                .contains(CameraCapability::OpenGlTexture)
198        );
199    }
200
201    #[test]
202    fn empty_camera_interface_reports_unavailable() {
203        let camera = CameraInterface::default();
204
205        assert!(!camera.is_available());
206        assert!(!camera.start());
207        assert!(!camera.stop());
208        assert!(camera.capabilities().is_empty());
209        assert_eq!(camera.size(), CameraFrameSize::frontend_default());
210    }
211
212    #[test]
213    fn camera_texture_newtypes_preserve_raw_values() {
214        assert_eq!(CameraTextureId::new(7).get(), 7);
215        assert_eq!(CameraTextureTarget::new(0x0de1).get(), 0x0de1);
216    }
217}