1use std::ffi::{CStr, CString};
7use std::marker::PhantomPinned;
8use std::pin::Pin;
9
10use enumflags2::{BitFlags, bitflags};
11
12use crate::raw::{
13 self, RETRO_SIMD_AES, RETRO_SIMD_ASIMD, RETRO_SIMD_AVX, RETRO_SIMD_AVX2, RETRO_SIMD_CMOV,
14 RETRO_SIMD_MMX, RETRO_SIMD_MMXEXT, RETRO_SIMD_MOVBE, RETRO_SIMD_NEON, RETRO_SIMD_POPCNT,
15 RETRO_SIMD_PS, RETRO_SIMD_SSE, RETRO_SIMD_SSE2, RETRO_SIMD_SSE3, RETRO_SIMD_SSE4,
16 RETRO_SIMD_SSE42, RETRO_SIMD_SSSE3, RETRO_SIMD_VFPU, RETRO_SIMD_VFPV3, RETRO_SIMD_VFPV4,
17 RETRO_SIMD_VMX, RETRO_SIMD_VMX128,
18};
19
20#[bitflags]
21#[repr(u64)]
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum CpuFeature {
24 Sse = RETRO_SIMD_SSE,
25 Sse2 = RETRO_SIMD_SSE2,
26 Vmx = RETRO_SIMD_VMX,
27 Vmx128 = RETRO_SIMD_VMX128,
28 Avx = RETRO_SIMD_AVX,
29 Neon = RETRO_SIMD_NEON,
30 Sse3 = RETRO_SIMD_SSE3,
31 Ssse3 = RETRO_SIMD_SSSE3,
32 Mmx = RETRO_SIMD_MMX,
33 MmxExt = RETRO_SIMD_MMXEXT,
34 Sse4 = RETRO_SIMD_SSE4,
35 Sse42 = RETRO_SIMD_SSE42,
36 Avx2 = RETRO_SIMD_AVX2,
37 Vfpu = RETRO_SIMD_VFPU,
38 Ps = RETRO_SIMD_PS,
39 Aes = RETRO_SIMD_AES,
40 Vfpv3 = RETRO_SIMD_VFPV3,
41 Vfpv4 = RETRO_SIMD_VFPV4,
42 Popcnt = RETRO_SIMD_POPCNT,
43 Movbe = RETRO_SIMD_MOVBE,
44 Cmov = RETRO_SIMD_CMOV,
45 Asimd = RETRO_SIMD_ASIMD,
46}
47
48pub type CpuFeatures = BitFlags<CpuFeature>;
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct PerfTick(u64);
52
53impl PerfTick {
54 pub const fn from_ticks(ticks: u64) -> Self {
55 Self(ticks)
56 }
57
58 pub const fn as_ticks(self) -> u64 {
59 self.0
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct PerfTimeMicros(i64);
65
66impl PerfTimeMicros {
67 pub const fn from_micros(micros: i64) -> Self {
68 Self(micros)
69 }
70
71 pub const fn as_micros(self) -> i64 {
72 self.0
73 }
74}
75
76#[derive(Debug)]
77pub struct PerfCounter {
78 ident: CString,
79 raw: raw::retro_perf_counter,
80 _pin: PhantomPinned,
81}
82
83impl PerfCounter {
84 pub fn new(ident: impl AsRef<str>) -> Pin<Box<Self>> {
85 let ident = crate::sanitize_cstring(ident);
86 let raw = raw::retro_perf_counter {
87 ident: ident.as_ptr(),
88 ..raw::retro_perf_counter::default()
89 };
90
91 Box::pin(Self {
92 ident,
93 raw,
94 _pin: PhantomPinned,
95 })
96 }
97
98 pub fn ident(&self) -> &CStr {
99 self.ident.as_c_str()
100 }
101
102 pub fn is_registered(&self) -> bool {
103 self.raw.registered
104 }
105
106 pub fn last_start(&self) -> PerfTick {
107 PerfTick::from_ticks(self.raw.start)
108 }
109
110 pub fn total(&self) -> PerfTick {
111 PerfTick::from_ticks(self.raw.total)
112 }
113
114 pub fn call_count(&self) -> u64 {
115 self.raw.call_cnt
116 }
117
118 fn as_raw_mut(self: Pin<&mut Self>) -> *mut raw::retro_perf_counter {
119 unsafe { &mut self.get_unchecked_mut().raw }
122 }
123}
124
125#[derive(Clone, Copy, Debug, Default)]
126pub struct PerfInterface {
127 raw: raw::retro_perf_callback,
128}
129
130impl PerfInterface {
131 pub(crate) const fn from_raw(raw: raw::retro_perf_callback) -> Self {
132 Self { raw }
133 }
134
135 pub fn time_micros(&self) -> Option<PerfTimeMicros> {
136 self.raw
137 .get_time_usec
138 .map(|get_time_usec| PerfTimeMicros::from_micros(unsafe { get_time_usec() }))
139 }
140
141 pub fn tick_counter(&self) -> Option<PerfTick> {
142 self.raw
143 .get_perf_counter
144 .map(|get_perf_counter| PerfTick::from_ticks(unsafe { get_perf_counter() }))
145 }
146
147 pub fn cpu_features(&self) -> Option<CpuFeatures> {
148 self.raw
149 .get_cpu_features
150 .map(|get_cpu_features| CpuFeatures::from_bits_truncate(unsafe { get_cpu_features() }))
151 }
152
153 pub fn log(&self) -> bool {
154 if let Some(perf_log) = self.raw.perf_log {
155 unsafe { perf_log() };
156 true
157 } else {
158 false
159 }
160 }
161
162 pub fn register_counter(&self, mut counter: Pin<&mut PerfCounter>) -> bool {
163 let Some(perf_register) = self.raw.perf_register else {
164 return false;
165 };
166
167 unsafe { perf_register(counter.as_mut().as_raw_mut()) };
168 counter.as_ref().get_ref().is_registered()
169 }
170
171 pub fn start_counter(&self, mut counter: Pin<&mut PerfCounter>) -> bool {
172 let Some(perf_start) = self.raw.perf_start else {
173 return false;
174 };
175 if !counter.as_ref().get_ref().is_registered() {
176 return false;
177 }
178
179 unsafe { perf_start(counter.as_mut().as_raw_mut()) };
180 true
181 }
182
183 pub fn stop_counter(&self, mut counter: Pin<&mut PerfCounter>) -> bool {
184 let Some(perf_stop) = self.raw.perf_stop else {
185 return false;
186 };
187 if !counter.as_ref().get_ref().is_registered() {
188 return false;
189 }
190
191 unsafe { perf_stop(counter.as_mut().as_raw_mut()) };
192 true
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::{CpuFeature, CpuFeatures, PerfCounter, PerfTick, PerfTimeMicros};
199
200 #[test]
201 fn cpu_features_encode_libretro_simd_bits() {
202 let features = CpuFeatures::from(CpuFeature::Sse2) | CpuFeature::Avx2 | CpuFeature::Neon;
203
204 assert!(features.contains(CpuFeature::Sse2));
205 assert!(features.contains(CpuFeature::Avx2));
206 assert!(features.contains(CpuFeature::Neon));
207 assert_eq!(
208 features.bits(),
209 crate::raw::RETRO_SIMD_SSE2 | crate::raw::RETRO_SIMD_AVX2 | crate::raw::RETRO_SIMD_NEON
210 );
211 }
212
213 #[test]
214 fn perf_time_and_tick_newtypes_preserve_raw_values() {
215 assert_eq!(PerfTimeMicros::from_micros(-1).as_micros(), -1);
216 assert_eq!(PerfTick::from_ticks(42).as_ticks(), 42);
217 }
218
219 #[test]
220 fn perf_counter_owns_sanitized_identifier_storage() {
221 let counter = PerfCounter::new("hot\0path");
222
223 assert_eq!(
224 counter
225 .ident()
226 .to_str()
227 .expect("identifier should be utf-8"),
228 "hotpath"
229 );
230 assert!(!counter.is_registered());
231 assert_eq!(counter.call_count(), 0);
232 assert_eq!(counter.total(), PerfTick::from_ticks(0));
233 }
234}