use super::ServerCompositionVisual;
use crate::media::Colors;
use crate::platform::{IDrawingContextImpl, IDrawingContextLayerImpl, IPlatformRenderInterfaceContext, LtrbRect};
use crate::rendering::composition::server::{
IDirtyRectCollector, IDirtyRectTracker, ServerCompositionBitmapCache, SingleDirtyRectTracker,
};
use crate::{Matrix, PixelSize, Rect, Vector};
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
pub struct ServerCompositionVisualCache {
cache_mode: Rc<ServerCompositionBitmapCache>,
target_visual: Weak<ServerCompositionVisual>,
needs_full_re_render: Cell<bool>,
layer: RefCell<Option<Rc<dyn IDrawingContextLayerImpl>>>,
layer_created_with_context: RefCell<Option<Rc<dyn IPlatformRenderInterfaceContext>>>,
layer_has_text_antialiasing: Cell<bool>,
desired_layer_size: Cell<PixelSize>,
scale_x: Cell<f64>,
scale_y: Cell<f64>,
draw_at_offset: Cell<Vector>,
need_to_finalize_frame: Cell<bool>,
dirty_rect_tracker: Rc<SingleDirtyRectTracker>,
}
impl ServerCompositionVisualCache {
pub fn new(
visual: Weak<ServerCompositionVisual>,
cache_mode: Rc<ServerCompositionBitmapCache>,
) -> Rc<ServerCompositionVisualCache> {
let cache = Rc::new(ServerCompositionVisualCache {
cache_mode,
target_visual: visual,
needs_full_re_render: Cell::new(false),
layer: RefCell::new(None),
layer_created_with_context: RefCell::new(None),
layer_has_text_antialiasing: Cell::new(false),
desired_layer_size: Cell::new(PixelSize::default()),
scale_x: Cell::new(0.0),
scale_y: Cell::new(0.0),
draw_at_offset: Cell::new(Vector::default()),
need_to_finalize_frame: Cell::new(true),
dirty_rect_tracker: Rc::new(SingleDirtyRectTracker::new()),
});
cache.mark_for_full_re_render();
cache
}
pub fn is_dirty(&self) -> bool {
!self.dirty_rect_tracker.is_empty()
}
pub fn target_visual(&self) -> Option<Rc<ServerCompositionVisual>> {
self.target_visual.upgrade()
}
fn render_at_scale(&self) -> f64 {
self.cache_mode.render_at_scale()
}
fn snaps_to_device_pixels(&self) -> bool {
self.cache_mode.snaps_to_device_pixels()
}
fn enable_clear_type(&self) -> bool {
self.cache_mode.enable_clear_type()
}
pub fn free_resources(&self) {
if let Some(layer) = self.layer.borrow_mut().take() {
layer.dispose();
}
*self.layer_created_with_context.borrow_mut() = None;
}
pub fn invalidate_properties(&self) {
self.mark_for_full_re_render();
}
fn reset_dirty_rects(&self) {
self.need_to_finalize_frame.set(true);
self.dirty_rect_tracker.initialize(LtrbRect::INFINITE);
}
fn mark_for_full_re_render(&self) {
self.needs_full_re_render.set(true);
self.reset_dirty_rects();
}
fn is_close_real(a: f64, b: f64) -> bool {
((a - b) / (if b == 0.0 { 1.0 } else { b })).abs() < 10.0 * f32::EPSILON as f64
}
fn update_realization_dimensions(&self) -> bool {
let Some(target_visual) = self.target_visual() else { return false };
let (Some(root), Some(visual_bounds), Some(compositor)) =
(target_visual.root(), target_visual.sub_tree_bounds(), target_visual.compositor())
else {
return false;
};
let scale = root.scaling() * self.render_at_scale();
let max_size = compositor
.render_interface()
.value()
.max_offscreen_render_target_pixel_size()
.unwrap_or(PixelSize::new(16384, 16384));
let f_width = visual_bounds.width() * scale;
let mut u_width = f_width as i32;
if !Self::is_close_real(f_width, f_width) {
u_width += 1;
}
let f_height = visual_bounds.height() * scale;
let mut u_height = f_height as i32;
if !Self::is_close_real(f_height, f_height) {
u_height += 1;
}
self.scale_x.set(scale);
self.scale_y.set(scale);
if u_width > max_size.width {
self.scale_x.set(self.scale_x.get() * max_size.width as f64 / u_width as f64);
u_width = max_size.width;
}
if u_height > max_size.height {
self.scale_y.set(self.scale_y.get() * max_size.height as f64 / u_height as f64);
u_height = max_size.height;
}
self.draw_at_offset.set(Vector::new(-visual_bounds.left, -visual_bounds.top));
self.desired_layer_size.set(PixelSize::new(u_width, u_height));
true
}
pub fn draw(self: &Rc<Self>, outer_canvas: &mut dyn IDrawingContextImpl) -> (i32, i32) {
let Some(target_visual) = self.target_visual() else { return (0, 0) };
let Some(sub_tree_bounds) = target_visual.sub_tree_bounds() else { return (0, 0) };
let Some(compositor) = target_visual.compositor() else { return (0, 0) };
self.update_realization_dimensions();
let render_context = compositor.render_interface().value();
let recreate = {
let layer = self.layer.borrow();
match layer.as_ref() {
None => true,
Some(layer) => {
self.layer_has_text_antialiasing.get() != self.enable_clear_type()
|| layer.pixel_size() != self.desired_layer_size.get()
|| !self
.layer_created_with_context
.borrow()
.as_ref()
.is_some_and(|context| Rc::ptr_eq(context, &render_context))
}
}
};
if recreate {
if let Some(layer) = self.layer.borrow_mut().take() {
layer.dispose();
}
*self.layer_created_with_context.borrow_mut() = None;
let desired = self.desired_layer_size.get();
if desired.width < 1 || desired.height < 1 {
self.reset_dirty_rects();
return (0, 0);
}
*self.layer.borrow_mut() = Some(render_context.create_offscreen_render_target(
desired,
Vector::new(self.scale_x.get(), self.scale_x.get()),
self.enable_clear_type(),
));
self.layer_has_text_antialiasing.set(self.enable_clear_type());
*self.layer_created_with_context.borrow_mut() = Some(render_context);
self.needs_full_re_render.set(true);
}
let Some(layer) = self.layer.borrow().clone() else { return (0, 0) };
let layer_size = layer.pixel_size();
let full_frame_rect = LtrbRect::new(0.0, 0.0, layer_size.width as f64, layer_size.height as f64);
if self.needs_full_re_render.get() {
self.reset_dirty_rects();
self.add_rect(LtrbRect::INFINITE);
}
if self.need_to_finalize_frame.get() {
self.dirty_rect_tracker.finalize_frame(full_frame_rect);
self.need_to_finalize_frame.set(false);
}
let visual_local_bounds = sub_tree_bounds.to_rect();
let mut rv = (0, 0);
if !self.dirty_rect_tracker.is_empty() {
let mut ctx = layer.create_drawing_context();
let clipped = !self.needs_full_re_render.get();
if clipped {
self.dirty_rect_tracker.begin_draw(&mut *ctx);
}
ctx.clear(Colors::TRANSPARENT);
let offset = self.draw_at_offset.get();
ctx.set_transform(
Matrix::create_translation(offset.x, offset.y)
* Matrix::create_scale(self.scale_x.get(), self.scale_y.get()),
);
let tracker: Rc<dyn IDirtyRectTracker> = self.dirty_rect_tracker.clone();
rv = target_visual.render(
&mut *ctx,
self.dirty_rect_tracker.combined_rect(),
Some(tracker),
true,
false,
true,
);
if clipped {
self.dirty_rect_tracker.end_draw(&mut *ctx);
}
ctx.dispose();
}
self.needs_full_re_render.set(false);
let original_transform = outer_canvas.transform();
if self.snaps_to_device_pixels() {
let world_bounds = visual_local_bounds.transform_to_aabb(original_transform);
let snap_offset_x = world_bounds.x - world_bounds.x.floor();
let snap_offset_y = world_bounds.y - world_bounds.y.floor();
outer_canvas.set_transform(original_transform * Matrix::create_translation(-snap_offset_x, -snap_offset_y));
}
outer_canvas.draw_bitmap(
&*layer,
1.0,
Rect::new(0.0, 0.0, layer_size.width as f64, layer_size.height as f64),
visual_local_bounds,
);
if self.snaps_to_device_pixels() {
outer_canvas.set_transform(original_transform);
}
self.reset_dirty_rects();
rv
}
}
impl IDirtyRectCollector for ServerCompositionVisualCache {
fn add_rect(&self, rect: LtrbRect) {
self.need_to_finalize_frame.set(true);
let offset = self.draw_at_offset.get();
let (scale_x, scale_y) = (self.scale_x.get(), self.scale_y.get());
self.dirty_rect_tracker.add_rect(LtrbRect::new(
(rect.left + offset.x) * scale_x,
(rect.top + offset.y) * scale_y,
(rect.right + offset.x) * scale_x,
(rect.bottom + offset.y) * scale_y,
));
}
}