1use crate::drawing_context_impl::{CreateInfo, DrawingContextImpl};
2use crate::skia_sharp_extensions::{to_sk_alpha_type, to_sk_color_type_or_panic};
3use ferroui_base::platform::surfaces::{IFramebufferPlatformSurface, IFramebufferRenderTarget};
4use ferroui_base::platform::{
5 IDrawingContextImpl, ILockedFramebuffer, IRenderTarget, PlatformRenderTargetState,
6 RenderTargetDrawingContextProperties, RenderTargetProperties, RenderTargetSceneInfo,
7};
8use crate::text_rendering_settings::{TextRenderingSettings, TextRenderingSource};
9use skia_safe::{surfaces, Bitmap, ColorType, ImageInfo, Surface, SurfaceProps};
10use std::cell::{Cell, RefCell};
11use std::rc::Rc;
12
13struct SurfaceState {
16 current_image_info: Option<ImageInfo>,
17 current_props: Option<SurfaceProps>,
18 current_framebuffer_address: *mut u8,
19 framebuffer_surface: Option<Surface>,
20 conversion_shim: Option<Rc<PixelFormatConversionShim>>,
21}
22
23pub struct FramebufferRenderTarget {
26 use_scaled_drawing: bool,
27 state: RefCell<SurfaceState>,
28 render_target: RefCell<Option<Rc<dyn IFramebufferRenderTarget>>>,
29 had_conversion_shim: Cell<bool>,
30 text_rendering: TextRenderingSource,
31 frame_text_rendering: Rc<Cell<TextRenderingSettings>>,
33}
34
35impl FramebufferRenderTarget {
36 pub fn new(platform_surface: &dyn IFramebufferPlatformSurface, use_scaled_drawing: bool) -> Self {
38 Self::from_render_target(platform_surface.create_framebuffer_render_target(), use_scaled_drawing)
39 }
40
41 pub fn from_render_target(render_target: Rc<dyn IFramebufferRenderTarget>, use_scaled_drawing: bool) -> Self {
44 Self {
45 use_scaled_drawing,
46 state: RefCell::new(SurfaceState {
47 current_image_info: None,
48 current_props: None,
49 current_framebuffer_address: std::ptr::null_mut(),
50 framebuffer_surface: None,
51 conversion_shim: None,
52 }),
53 render_target: RefCell::new(Some(render_target)),
54 had_conversion_shim: Cell::new(false),
55 text_rendering: TextRenderingSource::default(),
56 frame_text_rendering: Rc::default(),
57 }
58 }
59
60 pub fn with_text_rendering(mut self, text_rendering: TextRenderingSource) -> Self {
64 self.text_rendering = text_rendering;
65 self
66 }
67
68 fn are_image_infos_compatible(current_image_info: &ImageInfo, desired_image_info: &ImageInfo) -> bool {
71 current_image_info.width() == desired_image_info.width()
72 && current_image_info.height() == desired_image_info.height()
73 && current_image_info.color_type() == desired_image_info.color_type()
74 }
75
76 fn create_surface(&self, desired_image_info: &ImageInfo, props: &SurfaceProps, framebuffer: &dyn ILockedFramebuffer) {
79 let mut state = self.state.borrow_mut();
80 let address = framebuffer.address();
81
82 if state.framebuffer_surface.is_some()
83 && state
84 .current_image_info
85 .as_ref()
86 .is_some_and(|current| Self::are_image_infos_compatible(current, desired_image_info))
87 && state.current_props.as_ref() == Some(props)
88 && state.current_framebuffer_address == address
89 {
90 return;
91 }
92
93 Self::free_surface(&mut state);
94
95 state.current_framebuffer_address = address;
96
97 if desired_image_info.width() <= 0 || desired_image_info.height() <= 0 {
99 panic!(
100 "Unable to create a surface with size {}x{}",
101 desired_image_info.width(),
102 desired_image_info.height()
103 );
104 }
105
106 let row_bytes = framebuffer.row_bytes().max(0) as usize;
107 let mut surface = None;
108
109 if !address.is_null() && row_bytes >= desired_image_info.min_row_bytes() {
110 let pixels =
116 unsafe { std::slice::from_raw_parts_mut(address, row_bytes * desired_image_info.height() as usize) };
117
118 surface = surfaces::wrap_pixels(desired_image_info, pixels, row_bytes, Some(props))
119 .map(|surface| unsafe { surface.release() });
123 }
124
125 if surface.is_none() {
128 let shim = Rc::new(PixelFormatConversionShim::new(desired_image_info.clone(), address, row_bytes, props));
129 surface = Some(shim.surface.clone());
130 state.conversion_shim = Some(shim);
131 }
132
133 state.framebuffer_surface = surface;
134 state.current_image_info = Some(desired_image_info.clone());
135 state.current_props = Some(*props);
136 }
137
138 fn free_surface(state: &mut SurfaceState) {
140 state.conversion_shim = None;
141 state.framebuffer_surface = None;
142 state.current_props = None;
143 state.current_framebuffer_address = std::ptr::null_mut();
144 }
145}
146
147impl IRenderTarget for FramebufferRenderTarget {
148 fn properties(&self) -> RenderTargetProperties {
149 RenderTargetProperties {
150 retains_previous_frame_contents: !self.had_conversion_shim.get()
151 && self.render_target.borrow().as_ref().is_some_and(|target| target.retains_frame_contents()),
152 is_suitable_for_direct_rendering: true,
153 }
154 }
155
156 fn create_drawing_context(
157 &self,
158 scene_info: &RenderTargetSceneInfo,
159 ) -> (Box<dyn IDrawingContextImpl>, RenderTargetDrawingContextProperties) {
160 let render_target =
161 self.render_target.borrow().clone().unwrap_or_else(|| panic!("FramebufferRenderTarget has been disposed"));
162
163 let (framebuffer, lock_properties) = render_target.lock(scene_info);
164 let size = framebuffer.size();
165 let framebuffer_image_info = ImageInfo::new(
166 (size.width, size.height),
167 to_sk_color_type_or_panic(framebuffer.format()),
168 to_sk_alpha_type(framebuffer.alpha_format()),
169 None,
170 );
171
172 let text_rendering = self.text_rendering.settings(scene_info.scaling, scene_info.transparency_level);
173 self.frame_text_rendering.set(text_rendering);
174 self.create_surface(&framebuffer_image_info, &text_rendering.surface_props(), &*framebuffer);
175
176 let (mut surface, conversion_shim) = {
177 let state = self.state.borrow();
178 (state.framebuffer_surface.clone().expect("the surface was just created"), state.conversion_shim.clone())
179 };
180
181 self.had_conversion_shim.set(self.had_conversion_shim.get() || conversion_shim.is_some());
182
183 {
184 let canvas = surface.canvas();
185 canvas.restore_to_count(1);
186 canvas.save();
187 canvas.reset_matrix();
188 }
189
190 let create_info = CreateInfo {
191 surface: Some(surface),
192 dpi: framebuffer.dpi(),
193 scale_drawing_to_dpi: self.use_scaled_drawing,
194 text_rendering,
195 frame_text_rendering: Some(self.frame_text_rendering.clone()),
196 ..CreateInfo::default()
197 };
198
199 let properties = RenderTargetDrawingContextProperties {
200 previous_frame_is_retained: !self.had_conversion_shim.get() && lock_properties.previous_frame_is_retained,
201 };
202
203 let mut disposables: Vec<Box<dyn FnOnce()>> = Vec::with_capacity(2);
204 if let Some(shim) = conversion_shim {
205 disposables.push(Box::new(move || shim.copy_surface()));
206 }
207 disposables.push(Box::new(move || framebuffer.dispose()));
208
209 (Box::new(DrawingContextImpl::new(create_info, disposables)), properties)
210 }
211
212 fn platform_render_target_state(&self) -> PlatformRenderTargetState {
213 match &*self.render_target.borrow() {
214 Some(render_target) => render_target.state(),
215 None => PlatformRenderTargetState::DISPOSED,
216 }
217 }
218
219 fn dispose(&self) {
220 if let Some(render_target) = self.render_target.borrow_mut().take() {
221 render_target.dispose();
222 }
223 Self::free_surface(&mut self.state.borrow_mut());
224 }
225}
226
227struct PixelFormatConversionShim {
231 surface: Surface,
232 destination_info: ImageInfo,
233 framebuffer_address: *mut u8,
234 framebuffer_row_bytes: usize,
235}
236
237impl PixelFormatConversionShim {
238 fn new(
239 destination_info: ImageInfo,
240 framebuffer_address: *mut u8,
241 framebuffer_row_bytes: usize,
242 props: &SurfaceProps,
243 ) -> Self {
244 if !Self::can_copy_to(destination_info.color_type()) {
246 panic!(
247 "Unable to create pixel format shim for conversion from {:?} to {:?}",
248 ColorType::N32,
249 destination_info.color_type()
250 );
251 }
252
253 let info = ImageInfo::new_n32_premul(destination_info.dimensions(), None);
254 let surface = surfaces::raster(&info, None, Some(props)).unwrap_or_else(|| {
255 panic!(
256 "Unable to create pixel format shim surface for conversion from {:?} to {:?}",
257 ColorType::N32,
258 destination_info.color_type()
259 )
260 });
261
262 Self { surface, destination_info, framebuffer_address, framebuffer_row_bytes }
263 }
264
265 fn can_copy_to(color_type: ColorType) -> bool {
267 if color_type == ColorType::Unknown {
268 return false;
269 }
270
271 let info = ImageInfo::new_n32_premul((1, 1), None).with_color_type(color_type);
272 Bitmap::new().try_alloc_pixels_info(&info, None)
273 }
274
275 fn copy_surface(&self) {
277 if self.framebuffer_address.is_null() {
278 return;
279 }
280
281 let row_bytes = self.framebuffer_row_bytes.max(self.destination_info.min_row_bytes());
282
283 let pixels = unsafe {
287 std::slice::from_raw_parts_mut(
288 self.framebuffer_address,
289 row_bytes * self.destination_info.height() as usize,
290 )
291 };
292
293 self.surface.clone().read_pixels(&self.destination_info, pixels, row_bytes, (0, 0));
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use skia_safe::{AlphaType, Color};
301
302 #[test]
303 fn conversion_shim_copies_the_frame_in_the_destination_format() {
304 let destination_info = ImageInfo::new((4, 4), ColorType::RGBA8888, AlphaType::Unpremul, None);
307 let row_bytes = 4 * 4 + 4;
308 let mut framebuffer = vec![0u8; row_bytes * 4];
309
310 let shim = PixelFormatConversionShim::new(
311 destination_info,
312 framebuffer.as_mut_ptr(),
313 row_bytes,
314 &SurfaceProps::default(),
315 );
316 shim.surface.clone().canvas().clear(Color::from_argb(128, 255, 0, 0));
317 shim.copy_surface();
318 drop(shim);
319
320 for y in 0..4 {
321 let row = &framebuffer[y * row_bytes..];
322 assert_eq!([255, 0, 0, 128], row[4..8]);
323 assert_eq!([0, 0, 0, 0], row[16..20]);
325 }
326 }
327
328 #[test]
329 fn conversion_shim_rejects_unknown_color_types() {
330 assert!(!PixelFormatConversionShim::can_copy_to(ColorType::Unknown));
331 assert!(PixelFormatConversionShim::can_copy_to(ColorType::RGB565));
332 }
333}