Skip to main content

libretro_core/
perf.rs

1//! Performance counter, timer, and CPU feature wrappers.
2//!
3//! The types in this module keep frontend performance counters pinned and make
4//! counter ticks, microsecond times, and CPU feature flags explicit.
5
6use 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        // SAFETY: This method never moves the pinned counter; it only exposes
120        // its stable raw storage to the frontend callback for in-place updates.
121        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}