retroglyph_software/
surface_native.rs1#![allow(clippy::redundant_pub_crate)]
13
14use retroglyph_window::WindowHandle;
15use std::num::NonZeroU32;
16use std::sync::Arc;
17
18pub(crate) struct WindowSurface {
26 _context: softbuffer::Context<Arc<dyn WindowHandle>>,
27 surface: softbuffer::Surface<Arc<dyn WindowHandle>, Arc<dyn WindowHandle>>,
28 width: u32,
31}
32
33#[derive(Debug)]
35#[non_exhaustive]
36pub enum SurfaceError {
37 Context(softbuffer::SoftBufferError),
39 Surface(softbuffer::SoftBufferError),
41}
42
43impl core::fmt::Display for SurfaceError {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 match self {
46 Self::Context(_) => write!(f, "softbuffer context creation failed"),
47 Self::Surface(_) => write!(f, "softbuffer surface creation or presentation failed"),
48 }
49 }
50}
51
52impl retroglyph_window::RecoverableError for SurfaceError {}
57
58impl std::error::Error for SurfaceError {
59 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
60 match self {
61 Self::Context(e) | Self::Surface(e) => Some(e),
62 }
63 }
64}
65
66impl WindowSurface {
67 pub(crate) fn new(window: Arc<dyn WindowHandle>) -> Result<Self, SurfaceError> {
74 let context = softbuffer::Context::new(window.clone()).map_err(SurfaceError::Context)?;
75 let surface = softbuffer::Surface::new(&context, window).map_err(SurfaceError::Surface)?;
76 Ok(Self {
77 _context: context,
78 surface,
79 width: 0,
80 })
81 }
82
83 pub(crate) fn resize(&mut self, width: u32, height: u32) {
85 if let (Some(w), Some(h)) = (NonZeroU32::new(width), NonZeroU32::new(height)) {
86 let _ = self.surface.resize(w, h);
87 self.width = width;
88 }
89 }
90
91 pub(crate) fn present(
103 &mut self,
104 pixels: &[u32],
105 damage: (u32, u32),
106 ) -> Result<(), SurfaceError> {
107 let mut buffer = self.surface.buffer_mut().map_err(SurfaceError::Surface)?;
108 if needs_full_present_fallback(pixels.len(), buffer.len()) {
109 buffer.fill(0);
110 return buffer.present().map_err(SurfaceError::Surface);
111 }
112 buffer.copy_from_slice(pixels);
113 match damage_rect(self.width, damage) {
114 Some(rect) => buffer
115 .present_with_damage(&[rect])
116 .map_err(SurfaceError::Surface),
117 None => buffer.present().map_err(SurfaceError::Surface),
118 }
119 }
120}
121
122const fn needs_full_present_fallback(pixels_len: usize, buffer_len: usize) -> bool {
126 pixels_len != buffer_len
127}
128
129const fn damage_rect(width: u32, damage: (u32, u32)) -> Option<softbuffer::Rect> {
134 let (y0, y1) = damage;
135 match (
136 NonZeroU32::new(width),
137 NonZeroU32::new(y1.saturating_sub(y0)),
138 ) {
139 (Some(width), Some(height)) => Some(softbuffer::Rect {
140 x: 0,
141 y: y0,
142 width,
143 height,
144 }),
145 _ => None,
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::{NonZeroU32, damage_rect, needs_full_present_fallback};
152
153 #[test]
154 fn full_present_fallback_on_length_mismatch() {
155 assert!(needs_full_present_fallback(3, 4));
156 assert!(needs_full_present_fallback(0, 4));
157 }
158
159 #[test]
160 fn no_fallback_when_lengths_match() {
161 assert!(!needs_full_present_fallback(4, 4));
162 assert!(!needs_full_present_fallback(0, 0));
163 }
164
165 #[test]
166 fn damage_rect_converts_band_to_full_width_rect() {
167 let rect = damage_rect(80, (4, 10)).expect("non-degenerate band");
168 assert_eq!(rect.x, 0);
169 assert_eq!(rect.y, 4);
170 assert_eq!(rect.width, NonZeroU32::new(80).unwrap());
171 assert_eq!(rect.height, NonZeroU32::new(6).unwrap());
172 }
173
174 #[test]
175 fn damage_rect_none_when_width_is_zero() {
176 assert!(damage_rect(0, (0, 10)).is_none());
177 }
178
179 #[test]
180 fn damage_rect_none_when_band_is_empty() {
181 assert!(damage_rect(80, (5, 5)).is_none());
182 }
183
184 #[test]
185 fn damage_rect_none_when_band_is_inverted() {
186 assert!(damage_rect(80, (10, 5)).is_none());
188 }
189}