use std::sync::Arc;
use std::time::Instant;
use lamco_pipewire::{convert_format, FfiDamageRegion, PixelFormat, VideoFrame};
use parking_lot::RwLock;
const RDP_BITMAP_ALIGNMENT: usize = 64;
const BUFFER_POOL_SIZE: usize = 8;
const DAMAGE_THRESHOLD: f32 = 0.75;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RdpPixelFormat {
BgrX32,
Bgr24,
Rgb16,
Rgb15,
}
impl RdpPixelFormat {
pub fn bytes_per_pixel(&self) -> usize {
match self {
Self::BgrX32 => 4,
Self::Bgr24 => 3,
Self::Rgb16 | Self::Rgb15 => 2,
}
}
pub fn from_pixel_format(format: PixelFormat) -> Self {
match format {
PixelFormat::BGRA | PixelFormat::BGRx => Self::BgrX32,
PixelFormat::RGBA | PixelFormat::RGBx => Self::BgrX32,
PixelFormat::RGB | PixelFormat::BGR => Self::Bgr24,
PixelFormat::NV12 | PixelFormat::YUY2 | PixelFormat::I420 => Self::BgrX32,
PixelFormat::GRAY8 => Self::BgrX32, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rectangle {
pub left: u16,
pub top: u16,
pub right: u16,
pub bottom: u16,
}
impl Rectangle {
pub fn new(left: u16, top: u16, right: u16, bottom: u16) -> Self {
Self {
left,
top,
right,
bottom,
}
}
pub fn width(&self) -> u16 {
self.right.saturating_sub(self.left)
}
pub fn height(&self) -> u16 {
self.bottom.saturating_sub(self.top)
}
pub fn area(&self) -> u32 {
self.width() as u32 * self.height() as u32
}
pub fn intersects(&self, other: &Rectangle) -> bool {
!(self.right <= other.left || other.right <= self.left || self.bottom <= other.top || other.bottom <= self.top)
}
pub fn merge(&mut self, other: &Rectangle) {
self.left = self.left.min(other.left);
self.top = self.top.min(other.top);
self.right = self.right.max(other.right);
self.bottom = self.bottom.max(other.bottom);
}
}
impl From<FfiDamageRegion> for Rectangle {
fn from(damage: FfiDamageRegion) -> Self {
Self {
left: damage.x as u16,
top: damage.y as u16,
right: ((damage.x as u32) + damage.width) as u16,
bottom: ((damage.y as u32) + damage.height) as u16,
}
}
}
#[derive(Debug, Clone)]
pub struct BitmapData {
pub rectangle: Rectangle,
pub format: RdpPixelFormat,
pub data: Vec<u8>,
pub compressed: bool,
}
#[derive(Debug, Clone)]
pub struct BitmapUpdate {
pub rectangles: Vec<BitmapData>,
}
#[derive(Clone)]
struct PooledBuffer {
data: Vec<u8>,
capacity: usize,
last_used: Instant,
}
struct BufferPool {
buffers: Vec<Option<PooledBuffer>>,
free_indices: Vec<usize>,
}
impl BufferPool {
fn new(size: usize) -> Self {
let mut buffers = Vec::with_capacity(size);
let mut free_indices = Vec::with_capacity(size);
for i in 0..size {
buffers.push(None);
free_indices.push(i);
}
Self { buffers, free_indices }
}
fn acquire(&mut self, size: usize) -> Vec<u8> {
for (idx, buffer_opt) in self.buffers.iter_mut().enumerate() {
if let Some(buffer) = buffer_opt {
if buffer.capacity >= size && self.free_indices.contains(&idx) {
self.free_indices.retain(|&i| i != idx);
buffer.last_used = Instant::now();
let mut data = std::mem::take(&mut buffer.data);
data.clear();
data.resize(size, 0);
return data;
}
}
}
let aligned_size = align_to_boundary(size, RDP_BITMAP_ALIGNMENT);
let mut buffer = Vec::with_capacity(aligned_size);
buffer.resize(size, 0);
buffer
}
fn release(&mut self, mut buffer: Vec<u8>) {
let capacity = buffer.capacity();
buffer.clear();
if let Some(idx) = self.free_indices.pop() {
self.buffers[idx] = Some(PooledBuffer {
data: buffer,
capacity,
last_used: Instant::now(),
});
} else {
let mut oldest_idx = 0;
let mut oldest_time = Instant::now();
for (idx, buffer_opt) in self.buffers.iter().enumerate() {
if let Some(buf) = buffer_opt {
if buf.last_used < oldest_time {
oldest_time = buf.last_used;
oldest_idx = idx;
}
}
}
self.buffers[oldest_idx] = Some(PooledBuffer {
data: buffer,
capacity,
last_used: Instant::now(),
});
}
}
}
struct DamageTracker {
regions: Vec<Rectangle>,
full_update: bool,
screen_width: u16,
screen_height: u16,
}
impl DamageTracker {
fn new(width: u16, height: u16) -> Self {
Self {
regions: Vec::new(),
full_update: false,
screen_width: width,
screen_height: height,
}
}
fn add_damage(&mut self, region: Rectangle) {
if self.full_update {
return;
}
let total_area = self.screen_width as u32 * self.screen_height as u32;
let damage_area = region.area();
if damage_area as f32 / total_area as f32 > DAMAGE_THRESHOLD {
self.full_update = true;
self.regions.clear();
return;
}
let mut merged = false;
for existing in &mut self.regions {
if existing.intersects(®ion) {
existing.merge(®ion);
merged = true;
break;
}
}
if !merged {
self.regions.push(region);
}
self.consolidate_regions();
}
fn consolidate_regions(&mut self) {
if self.regions.len() < 2 {
return;
}
let mut consolidated = Vec::new();
let mut used = vec![false; self.regions.len()];
for i in 0..self.regions.len() {
if used[i] {
continue;
}
let mut current = self.regions[i];
used[i] = true;
for j in (i + 1)..self.regions.len() {
if used[j] {
continue;
}
if current.intersects(&self.regions[j]) {
current.merge(&self.regions[j]);
used[j] = true;
}
}
consolidated.push(current);
}
self.regions = consolidated;
}
fn get_damage_regions(&self) -> Vec<Rectangle> {
if self.full_update {
vec![Rectangle::new(0, 0, self.screen_width, self.screen_height)]
} else {
self.regions.clone()
}
}
fn reset(&mut self) {
self.regions.clear();
self.full_update = false;
}
}
#[derive(Debug, Clone, Default)]
pub struct ConversionStats {
pub frames_converted: u64,
pub bytes_processed: u64,
pub conversion_time_ns: u64,
pub simd_optimized_frames: u64,
}
impl ConversionStats {
pub fn avg_conversion_time_ms(&self) -> f64 {
if self.frames_converted == 0 {
0.0
} else {
(self.conversion_time_ns as f64 / self.frames_converted as f64) / 1_000_000.0
}
}
pub fn throughput_mbps(&self) -> f64 {
if self.conversion_time_ns == 0 {
0.0
} else {
(self.bytes_processed as f64 / 1_048_576.0) / (self.conversion_time_ns as f64 / 1_000_000_000.0)
}
}
}
pub struct BitmapConverter {
buffer_pool: Arc<RwLock<BufferPool>>,
damage_tracker: Arc<RwLock<DamageTracker>>,
last_frame_hash: u64,
enable_simd: bool,
stats: Arc<RwLock<ConversionStats>>,
}
impl BitmapConverter {
pub fn new(width: u16, height: u16) -> Self {
Self {
buffer_pool: Arc::new(RwLock::new(BufferPool::new(BUFFER_POOL_SIZE))),
damage_tracker: Arc::new(RwLock::new(DamageTracker::new(width, height))),
last_frame_hash: 0,
enable_simd: Self::detect_simd_support(),
stats: Arc::new(RwLock::new(ConversionStats::default())),
}
}
fn detect_simd_support() -> bool {
#[cfg(target_arch = "x86_64")]
{
is_x86_feature_detected!("avx2") || is_x86_feature_detected!("ssse3")
}
#[cfg(target_arch = "aarch64")]
{
std::arch::is_aarch64_feature_detected!("neon")
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
false
}
}
pub fn convert_frame(&mut self, frame: &VideoFrame) -> Result<BitmapUpdate, ConversionError> {
let start_time = Instant::now();
if !frame.is_valid() {
return Err(ConversionError::InvalidFrame(
"Frame is corrupted or incomplete".to_string(),
));
}
let frame_hash = self.calculate_frame_hash(&frame.data);
if frame_hash == self.last_frame_hash {
return Ok(BitmapUpdate { rectangles: vec![] });
}
self.last_frame_hash = frame_hash;
if !frame.damage_regions.is_empty() {
let mut tracker = self.damage_tracker.write();
for damage in &frame.damage_regions {
tracker.add_damage(Rectangle::from(*damage));
}
} else {
self.damage_tracker.write().full_update = true;
}
let damage_regions = self.damage_tracker.read().get_damage_regions();
let rdp_format = RdpPixelFormat::from_pixel_format(frame.format);
let output_size = Self::calculate_output_size(frame.width, frame.height, rdp_format);
let mut output_buffer = self.buffer_pool.write().acquire(output_size);
self.convert_frame_data(frame, &mut output_buffer, rdp_format)?;
let mut rectangles = Vec::new();
for region in damage_regions {
let bitmap_data = self.create_bitmap_data(&output_buffer, region, frame.width, frame.height, rdp_format)?;
rectangles.push(bitmap_data);
}
let elapsed = start_time.elapsed();
let mut stats = self.stats.write();
stats.frames_converted += 1;
stats.bytes_processed += frame.data_size() as u64;
stats.conversion_time_ns += elapsed.as_nanos() as u64;
if self.enable_simd {
stats.simd_optimized_frames += 1;
}
self.buffer_pool.write().release(output_buffer);
self.damage_tracker.write().reset();
Ok(BitmapUpdate { rectangles })
}
fn calculate_frame_hash(&self, data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in data.iter().step_by(64) {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
fn calculate_output_size(width: u32, height: u32, format: RdpPixelFormat) -> usize {
let stride = calculate_rdp_stride(width, format);
(stride * height) as usize
}
fn convert_frame_data(
&self,
frame: &VideoFrame,
dst: &mut [u8],
rdp_format: RdpPixelFormat,
) -> Result<(), ConversionError> {
let target_format = match rdp_format {
RdpPixelFormat::BgrX32 => PixelFormat::BGRx,
RdpPixelFormat::Bgr24 => PixelFormat::BGR,
RdpPixelFormat::Rgb16 | RdpPixelFormat::Rgb15 => {
return Err(ConversionError::UnsupportedFormat(frame.format));
}
};
let dst_stride = calculate_rdp_stride(frame.width, rdp_format);
convert_format(
&frame.data,
dst,
frame.format,
target_format,
frame.width,
frame.height,
frame.stride,
dst_stride,
)
.map_err(|e| ConversionError::ConversionFailed(e.to_string()))
}
fn create_bitmap_data(
&self,
buffer: &[u8],
region: Rectangle,
width: u32,
height: u32,
format: RdpPixelFormat,
) -> Result<BitmapData, ConversionError> {
let region_width = region.width() as u32;
let region_height = region.height() as u32;
let stride = calculate_rdp_stride(width, format);
let bpp = format.bytes_per_pixel() as u32;
let mut region_data = Vec::with_capacity((region_width * region_height * bpp) as usize);
for y in region.top..region.bottom {
if y >= height as u16 {
break;
}
let src_offset = (y as u32 * stride + region.left as u32 * bpp) as usize;
let row_size = (region_width * bpp) as usize;
if src_offset + row_size <= buffer.len() {
region_data.extend_from_slice(&buffer[src_offset..src_offset + row_size]);
} else {
return Err(ConversionError::BufferTooSmall {
required: src_offset + row_size,
provided: buffer.len(),
});
}
}
Ok(BitmapData {
rectangle: region,
format,
data: region_data,
compressed: false,
})
}
pub fn force_full_update(&mut self) {
self.damage_tracker.write().full_update = true;
}
pub fn get_statistics(&self) -> ConversionStats {
self.stats.read().clone()
}
pub fn reset_statistics(&mut self) {
*self.stats.write() = ConversionStats::default();
}
}
fn calculate_rdp_stride(width: u32, format: RdpPixelFormat) -> u32 {
let bpp = format.bytes_per_pixel() as u32;
let row_bytes = width * bpp;
align_to_boundary(row_bytes as usize, RDP_BITMAP_ALIGNMENT) as u32
}
fn align_to_boundary(value: usize, boundary: usize) -> usize {
(value + boundary - 1) & !(boundary - 1)
}
#[derive(Debug, thiserror::Error)]
pub enum ConversionError {
#[error("Unsupported pixel format: {0:?}")]
UnsupportedFormat(PixelFormat),
#[error("Buffer too small: required {required} bytes, provided {provided} bytes")]
BufferTooSmall { required: usize, provided: usize },
#[error("Invalid frame: {0}")]
InvalidFrame(String),
#[error("Conversion failed: {0}")]
ConversionFailed(String),
#[error("Allocation failed for {0} bytes")]
AllocationFailed(usize),
}
#[allow(unsafe_code)]
unsafe impl Send for BitmapConverter {}
#[allow(unsafe_code)]
unsafe impl Send for BufferPool {}
#[allow(unsafe_code)]
unsafe impl Send for DamageTracker {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rectangle_operations() {
let rect1 = Rectangle::new(0, 0, 100, 100);
let rect2 = Rectangle::new(50, 50, 150, 150);
assert_eq!(rect1.width(), 100);
assert_eq!(rect1.height(), 100);
assert_eq!(rect1.area(), 10000);
assert!(rect1.intersects(&rect2));
let mut merged = rect1;
merged.merge(&rect2);
assert_eq!(merged, Rectangle::new(0, 0, 150, 150));
}
#[test]
fn test_damage_tracker() {
let mut tracker = DamageTracker::new(1920, 1080);
tracker.add_damage(Rectangle::new(0, 0, 100, 100));
tracker.add_damage(Rectangle::new(200, 200, 300, 300));
assert_eq!(tracker.regions.len(), 2);
tracker.add_damage(Rectangle::new(50, 50, 150, 150));
assert_eq!(tracker.regions.len(), 2);
tracker.reset();
assert_eq!(tracker.regions.len(), 0);
}
#[test]
fn test_buffer_pool() {
let mut pool = BufferPool::new(4);
let buf1 = pool.acquire(1024);
assert_eq!(buf1.len(), 1024);
let buf2 = pool.acquire(2048);
assert_eq!(buf2.len(), 2048);
pool.release(buf1);
let buf3 = pool.acquire(1024);
assert_eq!(buf3.len(), 1024);
}
#[test]
fn test_rdp_pixel_format() {
assert_eq!(RdpPixelFormat::BgrX32.bytes_per_pixel(), 4);
assert_eq!(RdpPixelFormat::Bgr24.bytes_per_pixel(), 3);
assert_eq!(RdpPixelFormat::Rgb16.bytes_per_pixel(), 2);
assert_eq!(
RdpPixelFormat::from_pixel_format(PixelFormat::BGRA),
RdpPixelFormat::BgrX32
);
}
#[test]
fn test_stride_calculation() {
assert_eq!(calculate_rdp_stride(1920, RdpPixelFormat::BgrX32), 7680);
assert_eq!(calculate_rdp_stride(1921, RdpPixelFormat::BgrX32), 7744); assert_eq!(calculate_rdp_stride(1920, RdpPixelFormat::Bgr24), 5760);
}
#[test]
fn test_conversion_stats() {
let mut stats = ConversionStats::default();
stats.frames_converted = 100;
stats.bytes_processed = 100_000_000;
stats.conversion_time_ns = 500_000_000;
assert_eq!(stats.avg_conversion_time_ms(), 5.0); assert!(stats.throughput_mbps() > 0.0);
}
#[test]
fn test_bitmap_converter_creation() {
let converter = BitmapConverter::new(1920, 1080);
let stats = converter.get_statistics();
assert_eq!(stats.frames_converted, 0);
}
}