1use 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}