use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use smudge_core::geometry::PixelRect;
const HISTORY: usize = 3;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PresentStats {
pub buffer_age: u8,
pub with_damage: bool,
pub bytes_copied: u64,
pub surface_width: u32,
pub surface_height: u32,
pub raster: Duration,
pub acquire: Duration,
pub present: Duration,
pub renderer: &'static str,
}
#[derive(Clone, Debug, Default)]
pub struct PresentStatsHandle {
slot: Rc<RefCell<PresentStats>>,
#[cfg(feature = "gpu")]
adapter: Rc<RefCell<Option<crate::AdapterSummary>>>,
}
impl PresentStatsHandle {
pub fn new() -> PresentStatsHandle {
PresentStatsHandle::default()
}
pub fn get(&self) -> PresentStats {
*self.slot.borrow()
}
pub(crate) fn store(&self, stats: PresentStats) {
*self.slot.borrow_mut() = stats;
}
#[cfg(feature = "gpu")]
pub fn adapter(&self) -> Option<crate::AdapterSummary> {
self.adapter.borrow().clone()
}
#[cfg(feature = "gpu")]
pub(crate) fn set_adapter(&self, adapter: crate::AdapterSummary) {
*self.adapter.borrow_mut() = Some(adapter);
}
}
pub(crate) fn copy_rect(
source: &[u8],
source_width: u32,
destination: &mut [u32],
rect: PixelRect,
) {
let stride = source_width as usize;
let width = rect.width as usize;
for y in rect.y..rect.y + rect.height {
let source_row = &source[(y as usize * stride + rect.x as usize) * 4..][..width * 4];
let destination_row = &mut destination[y as usize * stride + rect.x as usize..][..width];
for (pixel, rgba) in destination_row
.iter_mut()
.zip(source_row.as_chunks::<4>().0)
{
*pixel = u32::from(rgba[0]) << 16 | u32::from(rgba[1]) << 8 | u32::from(rgba[2]);
}
}
}
pub(crate) fn tint_rects(buffer: &mut [u32], surface_width: u32, rects: &[PixelRect]) {
for rect in rects {
let row_width = rect.width as usize;
for y in rect.y..rect.y + rect.height {
let row =
&mut buffer[y as usize * surface_width as usize + rect.x as usize..][..row_width];
for pixel in row {
let red = tinted(*pixel >> 16 & 0xff, TINT_RGB[0]);
let green = tinted(*pixel >> 8 & 0xff, TINT_RGB[1]);
let blue = tinted(*pixel & 0xff, TINT_RGB[2]);
*pixel = red << 16 | green << 8 | blue;
}
}
}
}
const TINT_RGB: [u8; 3] = [255, 64, 64];
const TINT_ALPHA: u32 = 96;
fn tinted(channel: u32, tint: u8) -> u32 {
(channel * (255 - TINT_ALPHA) + u32::from(tint) * TINT_ALPHA + 127) / 255
}
pub(crate) struct DamageHistory {
entries: [Entry; HISTORY],
filled: usize,
}
struct Entry {
damage: Vec<PixelRect>,
full: bool,
}
impl Default for DamageHistory {
fn default() -> DamageHistory {
DamageHistory {
entries: [const {
Entry {
damage: Vec::new(),
full: false,
}
}; HISTORY],
filled: 0,
}
}
}
impl DamageHistory {
pub(crate) fn push(&mut self, damage: &[PixelRect], full: bool) {
self.entries.rotate_right(1);
let entry = &mut self.entries[0];
entry.damage.clear();
entry.damage.extend_from_slice(damage);
entry.full = full;
self.filled = (self.filled + 1).min(HISTORY);
}
}
pub(crate) fn present_skipped(damage: &[PixelRect], has_tick: bool, show_damage: bool) -> bool {
damage.is_empty() && has_tick && !show_damage
}
pub(crate) fn damage_region(
age: u8,
current: &[PixelRect],
current_full: bool,
history: &DamageHistory,
out: &mut Vec<PixelRect>,
) -> bool {
out.clear();
if !(1..=HISTORY as u8).contains(&age) || current_full || current.is_empty() {
return false;
}
let previous = age as usize - 1;
if previous > history.filled || history.entries[..previous].iter().any(|entry| entry.full) {
return false;
}
out.extend_from_slice(current);
for entry in &history.entries[..previous] {
for &rect in &entry.damage {
if !out.iter().any(|kept| kept.contains_rect(rect)) {
out.push(rect);
}
}
}
true
}
#[cfg(test)]
#[path = "present_tests.rs"]
mod tests;