retroglyph_software/
surface_native.rs1use retroglyph_window::presenter::WindowHandle;
9use std::num::NonZeroU32;
10use std::sync::Arc;
11
12pub(crate) struct WindowSurface {
20 _context: softbuffer::Context<Arc<dyn WindowHandle>>,
21 surface: softbuffer::Surface<Arc<dyn WindowHandle>, Arc<dyn WindowHandle>>,
22 width: u32,
25}
26
27#[derive(Debug)]
29#[non_exhaustive]
30pub enum SurfaceError {
31 Context(softbuffer::SoftBufferError),
33 Surface(softbuffer::SoftBufferError),
35}
36
37impl core::fmt::Display for SurfaceError {
38 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39 match self {
40 Self::Context(_) => write!(f, "softbuffer context creation failed"),
41 Self::Surface(_) => write!(f, "softbuffer surface creation or presentation failed"),
42 }
43 }
44}
45
46impl retroglyph_window::presenter::RecoverableError for SurfaceError {}
51
52impl std::error::Error for SurfaceError {
53 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
54 match self {
55 Self::Context(e) | Self::Surface(e) => Some(e),
56 }
57 }
58}
59
60impl WindowSurface {
61 pub(crate) fn new(window: Arc<dyn WindowHandle>) -> Result<Self, SurfaceError> {
68 let context = softbuffer::Context::new(window.clone()).map_err(SurfaceError::Context)?;
69 let surface = softbuffer::Surface::new(&context, window).map_err(SurfaceError::Surface)?;
70 Ok(Self {
71 _context: context,
72 surface,
73 width: 0,
74 })
75 }
76
77 pub(crate) fn resize(&mut self, width: u32, height: u32) {
79 if let (Some(w), Some(h)) = (NonZeroU32::new(width), NonZeroU32::new(height)) {
80 let _ = self.surface.resize(w, h);
81 self.width = width;
82 }
83 }
84
85 pub(crate) fn present(
97 &mut self,
98 pixels: &[u32],
99 damage: (u32, u32),
100 ) -> Result<(), SurfaceError> {
101 let mut buffer = self.surface.buffer_mut().map_err(SurfaceError::Surface)?;
102 if needs_full_present_fallback(pixels.len(), buffer.len()) {
103 buffer.fill(0);
104 return buffer.present().map_err(SurfaceError::Surface);
105 }
106 buffer.copy_from_slice(pixels);
107 match damage_rect(self.width, damage) {
108 Some(rect) => buffer
109 .present_with_damage(&[rect])
110 .map_err(SurfaceError::Surface),
111 None => buffer.present().map_err(SurfaceError::Surface),
112 }
113 }
114}
115
116const fn needs_full_present_fallback(pixels_len: usize, buffer_len: usize) -> bool {
120 pixels_len != buffer_len
121}
122
123const fn damage_rect(width: u32, damage: (u32, u32)) -> Option<softbuffer::Rect> {
128 let (y0, y1) = damage;
129 match (
130 NonZeroU32::new(width),
131 NonZeroU32::new(y1.saturating_sub(y0)),
132 ) {
133 (Some(width), Some(height)) => Some(softbuffer::Rect {
134 x: 0,
135 y: y0,
136 width,
137 height,
138 }),
139 _ => None,
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::{NonZeroU32, damage_rect, needs_full_present_fallback};
146
147 #[test]
148 fn full_present_fallback_on_length_mismatch() {
149 assert!(needs_full_present_fallback(3, 4));
150 assert!(needs_full_present_fallback(0, 4));
151 }
152
153 #[test]
154 fn no_fallback_when_lengths_match() {
155 assert!(!needs_full_present_fallback(4, 4));
156 assert!(!needs_full_present_fallback(0, 0));
157 }
158
159 #[test]
160 fn damage_rect_converts_band_to_full_width_rect() {
161 let rect = damage_rect(80, (4, 10)).expect("non-degenerate band");
162 assert_eq!(rect.x, 0);
163 assert_eq!(rect.y, 4);
164 assert_eq!(rect.width, NonZeroU32::new(80).unwrap());
165 assert_eq!(rect.height, NonZeroU32::new(6).unwrap());
166 }
167
168 #[test]
169 fn damage_rect_none_when_width_is_zero() {
170 assert!(damage_rect(0, (0, 10)).is_none());
171 }
172
173 #[test]
174 fn damage_rect_none_when_band_is_empty() {
175 assert!(damage_rect(80, (5, 5)).is_none());
176 }
177
178 #[test]
179 fn damage_rect_none_when_band_is_inverted() {
180 assert!(damage_rect(80, (10, 5)).is_none());
182 }
183}