Skip to main content

libretro_core/
hw_render.rs

1//! Hardware-rendering interface discovery and OpenGL context candidates.
2//!
3//! This module separates frontend context negotiation from typed GL command
4//! wrappers. Cores request `HwRenderConfig` candidates through `Environment`,
5//! then use `Runtime` and `glsym` after the frontend owns a live context.
6
7#[cfg(test)]
8use crate::HwContextType;
9use crate::HwRenderConfig;
10use crate::raw;
11use std::ffi::c_void;
12
13pub const OPENGL_COMPATIBILITY_HW_RENDER_LABEL: &str = "OpenGL/GLESv2/GLES2 candidate";
14pub const OPENGL_MODERN_PREFERRED_HW_RENDER_LABEL: &str = "modern-preferred OpenGL/GLES candidate";
15
16pub fn opengl_modern_preferred_hw_render_candidates() -> [HwRenderConfig; 5] {
17    [
18        // Generic OpenGL is the safest first request on old frontends that
19        // lack GET_PREFERRED_HW_RENDER. The active context can still promote
20        // to a modern renderer after context reset proves the live features.
21        HwRenderConfig::opengl().with_bottom_left_origin(true),
22        // Some older RetroArch GL drivers handle explicit GLES 2.0 version
23        // requests differently from the legacy OPENGLES2 enum.
24        HwRenderConfig::opengles_version(2, 0)
25            .with_depth(true)
26            .with_bottom_left_origin(true),
27        HwRenderConfig::opengles2()
28            .with_depth(true)
29            .with_bottom_left_origin(true),
30        // Strict modern requests are fallbacks for frontends that reject the
31        // tolerant families but can satisfy exact modern contexts.
32        HwRenderConfig::opengl_core(3, 3).with_bottom_left_origin(true),
33        HwRenderConfig::opengles3().with_bottom_left_origin(true),
34    ]
35}
36
37pub fn opengl_compatibility_hw_render_candidates() -> [HwRenderConfig; 3] {
38    [
39        // Generic OpenGL can map to a GLES2-class context on some libretro
40        // GL frontends; keep it first because the old-device probe proved
41        // this request form can present where legacy OPENGLES2 cannot.
42        HwRenderConfig::opengl()
43            .with_depth(true)
44            .with_bottom_left_origin(true),
45        // Some older RetroArch GL drivers handle explicit GLES 2.0 version
46        // requests differently from the legacy OPENGLES2 enum.
47        HwRenderConfig::opengles_version(2, 0)
48            .with_depth(true)
49            .with_bottom_left_origin(true),
50        HwRenderConfig::opengles2()
51            .with_depth(true)
52            .with_bottom_left_origin(true),
53    ]
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub enum HwRenderInterfaceType {
58    Vulkan,
59    D3d9,
60    D3d10,
61    D3d11,
62    D3d12,
63    GskitPs2,
64    Unknown(i32),
65}
66
67impl HwRenderInterfaceType {
68    pub(crate) fn from_raw(raw: i32) -> Self {
69        match raw {
70            value if value == raw::retro_hw_render_interface_type::Vulkan as i32 => Self::Vulkan,
71            value if value == raw::retro_hw_render_interface_type::D3d9 as i32 => Self::D3d9,
72            value if value == raw::retro_hw_render_interface_type::D3d10 as i32 => Self::D3d10,
73            value if value == raw::retro_hw_render_interface_type::D3d11 as i32 => Self::D3d11,
74            value if value == raw::retro_hw_render_interface_type::D3d12 as i32 => Self::D3d12,
75            value if value == raw::retro_hw_render_interface_type::GskitPs2 as i32 => {
76                Self::GskitPs2
77            }
78            value => Self::Unknown(value),
79        }
80    }
81
82    #[cfg(test)]
83    pub(crate) fn as_raw(self) -> i32 {
84        match self {
85            Self::Vulkan => raw::retro_hw_render_interface_type::Vulkan as i32,
86            Self::D3d9 => raw::retro_hw_render_interface_type::D3d9 as i32,
87            Self::D3d10 => raw::retro_hw_render_interface_type::D3d10 as i32,
88            Self::D3d11 => raw::retro_hw_render_interface_type::D3d11 as i32,
89            Self::D3d12 => raw::retro_hw_render_interface_type::D3d12 as i32,
90            Self::GskitPs2 => raw::retro_hw_render_interface_type::GskitPs2 as i32,
91            Self::Unknown(value) => value,
92        }
93    }
94}
95
96#[derive(Clone, Copy, Debug)]
97pub struct HwRenderInterface<'a> {
98    raw: &'a raw::retro_hw_render_interface,
99}
100
101impl<'a> HwRenderInterface<'a> {
102    pub(crate) fn from_raw(raw: &'a raw::retro_hw_render_interface) -> Self {
103        Self { raw }
104    }
105
106    pub fn interface_type(self) -> HwRenderInterfaceType {
107        HwRenderInterfaceType::from_raw(self.raw.interface_type)
108    }
109
110    pub fn interface_version(self) -> u32 {
111        self.raw.interface_version
112    }
113
114    pub fn as_base_ptr(self) -> *const c_void {
115        (self.raw as *const raw::retro_hw_render_interface).cast::<c_void>()
116    }
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
120pub enum HwRenderContextNegotiationInterfaceType {
121    Vulkan,
122    Unknown(i32),
123}
124
125impl HwRenderContextNegotiationInterfaceType {
126    #[cfg(test)]
127    pub(crate) fn from_raw(raw: i32) -> Self {
128        match raw {
129            value
130                if value
131                    == raw::retro_hw_render_context_negotiation_interface_type::Vulkan as i32 =>
132            {
133                Self::Vulkan
134            }
135            value => Self::Unknown(value),
136        }
137    }
138
139    pub(crate) fn as_raw(self) -> i32 {
140        match self {
141            Self::Vulkan => raw::retro_hw_render_context_negotiation_interface_type::Vulkan as i32,
142            Self::Unknown(value) => value,
143        }
144    }
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct HwRenderContextNegotiationInterface {
149    interface_type: HwRenderContextNegotiationInterfaceType,
150    interface_version: u32,
151}
152
153impl HwRenderContextNegotiationInterface {
154    pub fn new(
155        interface_type: HwRenderContextNegotiationInterfaceType,
156        interface_version: u32,
157    ) -> Self {
158        Self {
159            interface_type,
160            interface_version,
161        }
162    }
163
164    pub fn vulkan(interface_version: u32) -> Self {
165        Self::new(
166            HwRenderContextNegotiationInterfaceType::Vulkan,
167            interface_version,
168        )
169    }
170
171    pub fn interface_type(self) -> HwRenderContextNegotiationInterfaceType {
172        self.interface_type
173    }
174
175    pub fn interface_version(self) -> u32 {
176        self.interface_version
177    }
178
179    #[cfg(test)]
180    pub(crate) fn from_raw(raw: raw::retro_hw_render_context_negotiation_interface) -> Self {
181        Self {
182            interface_type: HwRenderContextNegotiationInterfaceType::from_raw(raw.interface_type),
183            interface_version: raw.interface_version,
184        }
185    }
186
187    pub(crate) fn as_raw(self) -> raw::retro_hw_render_context_negotiation_interface {
188        raw::retro_hw_render_context_negotiation_interface {
189            interface_type: self.interface_type.as_raw(),
190            interface_version: self.interface_version,
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn modern_preferred_candidates_keep_tolerant_paths_before_strict_modern_requests() {
201        let candidates = opengl_modern_preferred_hw_render_candidates();
202
203        assert_eq!(candidates[0].context_type, HwContextType::OpenGl);
204        assert!(candidates[0].bottom_left_origin);
205        assert_eq!(candidates[1].context_type, HwContextType::OpenGlEsVersion);
206        assert_eq!(candidates[1].version_major, 2);
207        assert_eq!(candidates[1].version_minor, 0);
208        assert!(candidates[1].depth);
209        assert!(candidates[1].bottom_left_origin);
210        assert_eq!(candidates[2].context_type, HwContextType::OpenGlEs2);
211        assert!(candidates[2].depth);
212        assert!(candidates[2].bottom_left_origin);
213        assert_eq!(candidates[3].context_type, HwContextType::OpenGlCore);
214        assert_eq!(candidates[3].version_major, 3);
215        assert_eq!(candidates[3].version_minor, 3);
216        assert!(candidates[3].bottom_left_origin);
217        assert_eq!(candidates[4].context_type, HwContextType::OpenGlEs3);
218        assert!(candidates[4].bottom_left_origin);
219    }
220
221    #[test]
222    fn compatibility_candidates_prefer_visible_request_forms_before_legacy_gles2() {
223        let candidates = opengl_compatibility_hw_render_candidates();
224
225        assert_eq!(candidates[0].context_type, HwContextType::OpenGl);
226        assert!(candidates[0].depth);
227        assert!(candidates[0].bottom_left_origin);
228        assert_eq!(candidates[1].context_type, HwContextType::OpenGlEsVersion);
229        assert_eq!(candidates[1].version_major, 2);
230        assert_eq!(candidates[1].version_minor, 0);
231        assert!(candidates[1].depth);
232        assert!(candidates[1].bottom_left_origin);
233        assert_eq!(candidates[2].context_type, HwContextType::OpenGlEs2);
234        assert!(candidates[2].depth);
235        assert!(candidates[2].bottom_left_origin);
236    }
237
238    #[test]
239    fn hw_render_interface_types_preserve_unknown_values() {
240        assert_eq!(
241            HwRenderInterfaceType::from_raw(raw::retro_hw_render_interface_type::D3d11 as i32),
242            HwRenderInterfaceType::D3d11
243        );
244        assert_eq!(HwRenderInterfaceType::Unknown(99).as_raw(), 99);
245    }
246
247    #[test]
248    fn hw_render_context_negotiation_interface_preserves_type_and_version() {
249        let interface = HwRenderContextNegotiationInterface::vulkan(2);
250        let raw = interface.as_raw();
251
252        assert_eq!(
253            raw.interface_type,
254            raw::retro_hw_render_context_negotiation_interface_type::Vulkan as i32
255        );
256        assert_eq!(raw.interface_version, 2);
257        assert_eq!(
258            HwRenderContextNegotiationInterface::from_raw(raw),
259            interface
260        );
261    }
262}