use std::sync::Arc;
use crate::framebuffer::Framebuffer;
use crate::gfx_ctx::GfxCtx;
use crate::lcd_coverage::LcdBuffer;
use super::*;
pub(super) fn paint_subtree_unified_backbuffer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
) -> bool {
let spec = widget.backbuffer_spec();
if spec.kind == BackbufferKind::None {
return false;
}
match spec.kind {
BackbufferKind::GlFbo if ctx.supports_retained_layers() => {
paint_subtree_gl_backbuffer(widget, ctx, include_overlay, spec);
true
}
BackbufferKind::SoftwareRgba | BackbufferKind::SoftwareLcd => {
if widget.backbuffer_cache_mut().is_some() {
paint_subtree_backbuffered(widget, ctx);
true
} else {
false
}
}
_ => false,
}
}
fn paint_subtree_gl_backbuffer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
spec: BackbufferSpec,
) {
let b = widget.bounds();
let layer_w = (b.width + spec.outsets.left + spec.outsets.right).max(1.0);
let layer_h = (b.height + spec.outsets.bottom + spec.outsets.top).max(1.0);
let subtree_needs_draw = widget.needs_draw();
let theme_epoch = crate::theme::current_visuals_epoch();
let typography_epoch = crate::font_settings::current_typography_epoch();
let async_state_epoch = crate::animation::async_state_epoch();
let (key, needs_draw) = {
let Some(state) = widget.backbuffer_state_mut() else {
paint_subtree_direct(widget, ctx);
return;
};
let w = layer_w.ceil().max(1.0) as u32;
let h = layer_h.ceil().max(1.0) as u32;
let changed = state.width != w || state.height != h || state.spec_kind != spec.kind;
let style_changed = state.theme_epoch != theme_epoch
|| state.typography_epoch != typography_epoch
|| state.async_state_epoch != async_state_epoch;
let needs = !spec.cached || state.dirty || changed || style_changed || subtree_needs_draw;
if changed {
state.width = w;
state.height = h;
state.spec_kind = spec.kind;
}
(state.id(), needs)
};
if spec.cached && !needs_draw {
ctx.save();
ctx.translate(-spec.outsets.left, -spec.outsets.bottom);
let composited = ctx.composite_retained_layer(key, layer_w, layer_h, spec.alpha);
ctx.restore();
if composited {
if let Some(state) = widget.backbuffer_state_mut() {
state.composite_count = state.composite_count.saturating_add(1);
}
return;
}
}
ctx.save();
ctx.translate(-spec.outsets.left, -spec.outsets.bottom);
if spec.cached {
ctx.push_retained_layer_with_alpha(key, layer_w, layer_h, spec.alpha);
} else {
ctx.push_layer_with_alpha(layer_w, layer_h, spec.alpha);
}
ctx.translate(spec.outsets.left, spec.outsets.bottom);
paint_subtree_direct_inner(widget, ctx, include_overlay, false);
ctx.pop_layer();
ctx.restore();
if let Some(state) = widget.backbuffer_state_mut() {
state.dirty = false;
state.theme_epoch = theme_epoch;
state.typography_epoch = typography_epoch;
state.async_state_epoch = async_state_epoch;
state.repaint_count = state.repaint_count.saturating_add(1);
state.composite_count = state.composite_count.saturating_add(1);
}
}
pub(super) fn paint_subtree_layer(
widget: &mut dyn Widget,
ctx: &mut dyn DrawCtx,
include_overlay: bool,
layer: crate::widget::CompositingLayer,
) {
let b = widget.bounds();
let layer_w = (b.width + layer.outset_left + layer.outset_right).max(1.0);
let layer_h = (b.height + layer.outset_bottom + layer.outset_top).max(1.0);
ctx.save();
ctx.translate(-layer.outset_left, -layer.outset_bottom);
ctx.push_layer_with_alpha(layer_w, layer_h, layer.alpha);
ctx.translate(layer.outset_left, layer.outset_bottom);
paint_subtree_direct_inner(widget, ctx, include_overlay, false);
ctx.pop_layer();
ctx.restore();
}
pub(super) fn paint_subtree_backbuffered(widget: &mut dyn Widget, ctx: &mut dyn DrawCtx) {
ctx.save();
ctx.snap_to_pixel();
use crate::widget::paint_timing as pt;
let timing = pt::enabled();
let mut tm = pt::PaintTiming::default();
let b = widget.bounds();
let (sx, sy) = ctx.transform().scaling_abs();
let dps_x = sx.max(1e-6);
let dps_y = sy.max(1e-6);
let band = widget.backbuffer_band();
let (over_top_phys, over_bottom_phys, blit_dy_phys) = match band {
Some(bd) => (
(bd.overscan_top.max(0.0) * dps_y).round() as u32,
(bd.overscan_bottom.max(0.0) * dps_y).round() as u32,
(bd.blit_dy * dps_y).round(),
),
None => (0, 0, 0.0),
};
let w_phys = (b.width * dps_x).ceil().max(1.0) as u32;
let base_h_phys = (b.height * dps_y).ceil().max(1.0) as u32;
let h_phys = base_h_phys + over_top_phys + over_bottom_phys;
let over_bottom_logical = over_bottom_phys as f64 / dps_y;
let w_logical = w_phys as f64 / dps_x;
let h_logical = h_phys as f64 / dps_y;
let mode = widget.backbuffer_mode();
let mode_is_lcd = matches!(mode, BackbufferMode::LcdCoverage);
let theme_epoch = crate::theme::current_visuals_epoch();
let typography_epoch = crate::font_settings::current_typography_epoch();
let async_state_epoch = crate::animation::async_state_epoch();
let (needs_raster, has_bitmap) = {
let cache = widget
.backbuffer_cache_mut()
.expect("backbuffered widget must return Some from backbuffer_cache_mut");
let cache_is_lcd = cache.lcd_alpha.is_some();
let needs = cache.dirty
|| cache.pixels.is_none()
|| cache.width != w_phys
|| cache.height != h_phys
|| cache_is_lcd != mode_is_lcd
|| cache.theme_epoch != theme_epoch
|| cache.typography_epoch != typography_epoch
|| cache.async_state_epoch != async_state_epoch;
(needs, cache.pixels.is_some())
};
if needs_raster {
let partial_reuse = band.is_some() && mode_is_lcd && {
let cache = widget.backbuffer_cache_mut().unwrap();
cache.pixels.is_some()
&& cache.theme_epoch == theme_epoch
&& cache.typography_epoch == typography_epoch
&& cache.async_state_epoch == async_state_epoch
&& cache
.lcd_buffer
.as_ref()
.map_or(false, |b| b.width() == w_phys && b.height() == h_phys)
};
{
widget.backbuffer_cache_mut().unwrap().partial_allowed = partial_reuse;
}
let mut reused_buf = if partial_reuse {
widget.backbuffer_cache_mut().unwrap().lcd_buffer.take()
} else {
None
};
let plane_len = (w_phys as usize) * (h_phys as usize) * 3;
let strip_rows: Option<(f64, f64)> = if partial_reuse {
let cache = widget.backbuffer_cache_mut().unwrap();
let planes_ok = cache
.pixels
.as_ref()
.map_or(false, |p| p.len() == plane_len)
&& cache
.lcd_alpha
.as_ref()
.map_or(false, |a| a.len() == plane_len);
if planes_ok {
band.and_then(|b| b.dirty_strip_y)
} else {
None
}
} else {
None
};
let strip_done = if let (BackbufferMode::LcdCoverage, Some((lo, hi))) = (mode, strip_rows) {
let mut buf = reused_buf
.take()
.expect("strip path requires the retained band buffer");
{
let t_setup = pt::start();
let mut sub = crate::lcd_gfx_ctx::LcdGfxCtx::new(&mut buf);
if (dps_x - 1.0).abs() > 1e-6 || (dps_y - 1.0).abs() > 1e-6 {
sub.scale(dps_x, dps_y);
}
if over_bottom_logical != 0.0 {
sub.translate(0.0, over_bottom_logical);
}
tm.lcd_ctx_setup_ms += pt::ms(&t_setup);
let t_paint = pt::start();
paint_subtree_direct_no_overlay(widget, &mut sub);
tm.widget_paint_ms += pt::ms(&t_paint);
}
let y_lo_phys = lo * dps_y + over_bottom_phys as f64;
let y_hi_phys = hi * dps_y + over_bottom_phys as f64;
let row_start =
((h_phys as f64 - y_hi_phys).floor() as i64 - 2).clamp(0, h_phys as i64) as u32;
let row_end =
((h_phys as f64 - y_lo_phys).ceil() as i64 + 2).clamp(0, h_phys as i64) as u32;
let cache = widget.backbuffer_cache_mut().unwrap();
{
let t_mkmut = pt::start();
let dst_color = Arc::make_mut(cache.pixels.as_mut().unwrap());
let dst_alpha = Arc::make_mut(cache.lcd_alpha.as_mut().unwrap());
tm.make_mut_ms += pt::ms(&t_mkmut);
let t_copy = pt::start();
buf.copy_rows_flipped_into(row_start, row_end, dst_color, dst_alpha);
tm.rowcopy_ms += pt::ms(&t_copy);
}
tm.plane_update_ms = tm.make_mut_ms + tm.rowcopy_ms;
tm.is_strip = true;
tm.strip_rows = Some((row_start, row_end));
cache.width = w_phys;
cache.height = h_phys;
cache.dirty = false;
cache.theme_epoch = theme_epoch;
cache.typography_epoch = typography_epoch;
cache.async_state_epoch = async_state_epoch;
cache.content_version = next_content_version();
cache.lcd_buffer = Some(buf);
true
} else {
false
};
let mut retained_buf: Option<LcdBuffer> = None;
if !strip_done {
let (pixels_bytes, lcd_alpha_bytes): (Vec<u8>, Option<Vec<u8>>) = match mode {
BackbufferMode::Rgba => {
let mut fb = Framebuffer::new(w_phys, h_phys);
{
let mut sub = GfxCtx::new(&mut fb);
sub.set_lcd_mode(false); if (dps_x - 1.0).abs() > 1e-6 || (dps_y - 1.0).abs() > 1e-6 {
sub.scale(dps_x, dps_y);
}
if over_bottom_logical != 0.0 {
sub.translate(0.0, over_bottom_logical);
}
paint_subtree_direct_no_overlay(widget, &mut sub);
}
let mut pixels = fb.pixels_flipped();
crate::framebuffer::unpremultiply_rgba_inplace(&mut pixels);
(pixels, None)
}
BackbufferMode::LcdCoverage => {
let t_setup = pt::start();
let mut buf = reused_buf.unwrap_or_else(|| LcdBuffer::new(w_phys, h_phys));
{
let mut sub = crate::lcd_gfx_ctx::LcdGfxCtx::new(&mut buf);
if (dps_x - 1.0).abs() > 1e-6 || (dps_y - 1.0).abs() > 1e-6 {
sub.scale(dps_x, dps_y);
}
if over_bottom_logical != 0.0 {
sub.translate(0.0, over_bottom_logical);
}
tm.lcd_ctx_setup_ms += pt::ms(&t_setup);
let t_paint = pt::start();
paint_subtree_direct_no_overlay(widget, &mut sub);
tm.widget_paint_ms += pt::ms(&t_paint);
}
let t_plane = pt::start();
let planes = (buf.color_plane_flipped(), Some(buf.alpha_plane_flipped()));
tm.plane_update_ms += pt::ms(&t_plane);
if band.is_some() {
retained_buf = Some(buf);
}
planes
}
};
let t_arc = pt::start();
let pixels = Arc::new(pixels_bytes);
let lcd_alpha = lcd_alpha_bytes.map(Arc::new);
tm.plane_update_ms += pt::ms(&t_arc);
let cache = widget.backbuffer_cache_mut().unwrap();
cache.pixels = Some(Arc::clone(&pixels));
cache.lcd_alpha = lcd_alpha.as_ref().map(Arc::clone);
cache.width = w_phys;
cache.height = h_phys;
cache.dirty = false;
cache.theme_epoch = theme_epoch;
cache.typography_epoch = typography_epoch;
cache.async_state_epoch = async_state_epoch;
cache.content_version = next_content_version();
cache.lcd_buffer = retained_buf;
}
}
tm.did_raster = needs_raster;
tm.h_phys = h_phys;
let cache = widget.backbuffer_cache_mut().unwrap();
let img_w = cache.width;
let img_h = cache.height;
let content_version = cache.content_version;
if band.is_some() {
let shift_logical = (blit_dy_phys - over_bottom_phys as f64) / dps_y;
ctx.save();
ctx.clip_rect(0.0, 0.0, b.width, b.height);
ctx.translate(0.0, shift_logical);
let t_blit = pt::start();
match (cache.pixels.as_ref(), cache.lcd_alpha.as_ref()) {
(Some(color), Some(alpha)) => {
ctx.draw_lcd_backbuffer_arc(
color,
alpha,
content_version,
img_w,
img_h,
0.0,
0.0,
w_logical,
h_logical,
);
}
(Some(bmp), None) => {
ctx.draw_image_rgba_arc(bmp, img_w, img_h, 0.0, 0.0, w_logical, h_logical);
}
_ => {}
}
tm.blit_ms += pt::ms(&t_blit);
ctx.restore();
} else {
let t_blit = pt::start();
match (cache.pixels.as_ref(), cache.lcd_alpha.as_ref()) {
(Some(color), Some(alpha)) => {
ctx.draw_lcd_backbuffer_arc(
color,
alpha,
content_version,
img_w,
img_h,
0.0,
0.0,
w_logical,
h_logical,
);
}
(Some(bmp), None) => {
ctx.draw_image_rgba_arc(bmp, img_w, img_h, 0.0, 0.0, w_logical, h_logical);
}
_ => {}
}
tm.blit_ms += pt::ms(&t_blit);
}
let _ = has_bitmap;
let t_overlay = pt::start();
widget.paint_overlay(ctx);
tm.overlay_ms += pt::ms(&t_overlay);
if timing {
tm.emit();
}
ctx.restore(); }