Skip to content

Commit 7f32137

Browse files
committed
feat(server): keep a framebuffer and tile-diff against it
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 1b27d81 commit 7f32137

File tree

2 files changed

+202
-25
lines changed

2 files changed

+202
-25
lines changed

crates/ironrdp-server/src/display.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use core::num::NonZeroU16;
33
use anyhow::Result;
44
use bytes::{Bytes, BytesMut};
55
use ironrdp_displaycontrol::pdu::DisplayControlMonitorLayout;
6+
use ironrdp_graphics::diff;
67
use ironrdp_pdu::pointer::PointerPositionAttribute;
78

89
#[rustfmt::skip]
@@ -94,6 +95,61 @@ impl TryInto<Framebuffer> for BitmapUpdate {
9495
}
9596
}
9697

98+
impl Framebuffer {
99+
pub fn update(&mut self, bitmap: &BitmapUpdate) {
100+
if self.format != bitmap.format {
101+
warn!("Bitmap format mismatch, unsupported");
102+
return;
103+
}
104+
let bpp = usize::from(self.format.bytes_per_pixel());
105+
let x = usize::from(bitmap.x);
106+
let y = usize::from(bitmap.y);
107+
let width = usize::from(bitmap.width.get());
108+
let height = usize::from(bitmap.height.get());
109+
110+
let start = y * self.stride + x * bpp;
111+
let end = start + (height - 1) * self.stride + width * bpp;
112+
let dst = &mut self.data[start..end];
113+
114+
for y in 0..height {
115+
let start = y * bitmap.stride;
116+
let end = start + width * bpp;
117+
let src = bitmap.data.slice(start..end);
118+
119+
let start = y * self.stride;
120+
let end = start + width * bpp;
121+
let dst = &mut dst[start..end];
122+
123+
dst.copy_from_slice(&src);
124+
}
125+
}
126+
127+
pub(crate) fn update_diffs(&mut self, bitmap: &BitmapUpdate, diffs: &[diff::Rect]) {
128+
for diff in diffs {
129+
let Some(x) = u16::try_from(diff.x).ok() else {
130+
continue;
131+
};
132+
let Some(y) = u16::try_from(diff.y).ok() else {
133+
continue;
134+
};
135+
let Some(width) = u16::try_from(diff.width).ok() else {
136+
continue;
137+
};
138+
let Some(width) = NonZeroU16::try_from(width).ok() else {
139+
continue;
140+
};
141+
let Some(height) = u16::try_from(diff.height).ok() else {
142+
continue;
143+
};
144+
let Some(height) = NonZeroU16::try_from(height).ok() else {
145+
continue;
146+
};
147+
let sub = bitmap.sub(x, y, width, height).unwrap();
148+
self.update(&sub)
149+
}
150+
}
151+
}
152+
97153
/// Bitmap Display Update
98154
///
99155
/// Bitmap updates are encoded using RDP 6.0 compression, fragmented and sent using
@@ -268,3 +324,42 @@ pub trait RdpServerDisplay: Send {
268324
debug!(?layout, "Requesting layout")
269325
}
270326
}
327+
328+
#[cfg(test)]
329+
mod tests {
330+
use super::{BitmapUpdate, Framebuffer};
331+
use core::num::NonZeroU16;
332+
use ironrdp_graphics::{diff::Rect, image_processing::PixelFormat};
333+
334+
#[test]
335+
fn framebuffer_update() {
336+
let width = NonZeroU16::new(800).unwrap();
337+
let height = NonZeroU16::new(600).unwrap();
338+
let fmt = PixelFormat::ABgr32;
339+
let bpp = usize::from(fmt.bytes_per_pixel());
340+
let mut fb = Framebuffer::new(width, height, fmt);
341+
342+
let width = 15;
343+
let stride = width * bpp;
344+
let height = 20;
345+
let data = vec![1u8; height * stride];
346+
let update = BitmapUpdate::new(
347+
1,
348+
2,
349+
NonZeroU16::new(15).unwrap(),
350+
NonZeroU16::new(20).unwrap(),
351+
fmt,
352+
data,
353+
stride,
354+
);
355+
let diffs = vec![Rect::new(2, 3, 4, 5)];
356+
fb.update_diffs(&update, &diffs);
357+
for y in 5..10 {
358+
for x in 3..7 {
359+
for b in 0..bpp {
360+
assert_eq!(fb.data[y * fb.stride + x * bpp + b], 1);
361+
}
362+
}
363+
}
364+
}
365+
}

crates/ironrdp-server/src/encoder/mod.rs

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use core::fmt;
2+
use core::num::NonZeroU16;
23

34
use anyhow::{Context, Result};
45
use ironrdp_acceptor::DesktopSize;
6+
use ironrdp_graphics::diff::{find_different_rects_sub, Rect};
57
use ironrdp_pdu::encode_vec;
68
use ironrdp_pdu::fast_path::UpdateCode;
79
use ironrdp_pdu::geometry::ExclusiveRectangle;
@@ -65,7 +67,7 @@ impl UpdateEncoder {
6567
pub(crate) fn update(&mut self, update: DisplayUpdate) -> EncoderIter<'_> {
6668
EncoderIter {
6769
encoder: self,
68-
update: Some(update),
70+
state: State::Start(update),
6971
}
7072
}
7173

@@ -129,14 +131,40 @@ impl UpdateEncoder {
129131
Ok(UpdateFragmenter::new(UpdateCode::PositionPointer, encode_vec(&pos)?))
130132
}
131133

132-
async fn bitmap(&mut self, bitmap: BitmapUpdate) -> Result<UpdateFragmenter> {
133-
// Clone to satisfy spawn_blocking 'static requirement
134-
// this should be cheap, even if using bitmap, since vec![] will be empty
135-
let mut updater = self.bitmap_updater.clone();
136-
let (res, bitmap) =
137-
tokio::task::spawn_blocking(move || time_warn!("Encoding bitmap", 10, (updater.handle(&bitmap), bitmap)))
138-
.await
139-
.unwrap();
134+
fn bitmap_diffs(&mut self, bitmap: &BitmapUpdate) -> Vec<Rect> {
135+
const USE_DIFFS: bool = true;
136+
137+
if let Some(Framebuffer {
138+
data,
139+
stride,
140+
width,
141+
height,
142+
..
143+
}) = USE_DIFFS.then_some(self.framebuffer.as_ref()).flatten()
144+
{
145+
find_different_rects_sub::<4>(
146+
data,
147+
*stride,
148+
width.get().into(),
149+
height.get().into(),
150+
&bitmap.data,
151+
bitmap.stride,
152+
bitmap.width.get().into(),
153+
bitmap.height.get().into(),
154+
bitmap.x.into(),
155+
bitmap.y.into(),
156+
)
157+
} else {
158+
vec![Rect {
159+
x: 0,
160+
y: 0,
161+
width: bitmap.width.get().into(),
162+
height: bitmap.height.get().into(),
163+
}]
164+
}
165+
}
166+
167+
fn bitmap_update_framebuffer(&mut self, bitmap: BitmapUpdate, diffs: &[Rect]) {
140168
if bitmap.x == 0
141169
&& bitmap.y == 0
142170
&& bitmap.width.get() == self.desktop_size.width
@@ -146,34 +174,88 @@ impl UpdateEncoder {
146174
Ok(framebuffer) => self.framebuffer = Some(framebuffer),
147175
Err(err) => warn!("Failed to convert bitmap to framebuffer: {}", err),
148176
}
177+
} else if let Some(fb) = self.framebuffer.as_mut() {
178+
fb.update_diffs(&bitmap, diffs);
149179
}
150-
res
151180
}
181+
182+
async fn bitmap(&mut self, bitmap: BitmapUpdate) -> Result<UpdateFragmenter> {
183+
// Clone to satisfy spawn_blocking 'static requirement
184+
// this should be cheap, even if using bitmap, since vec![] will be empty
185+
let mut updater = self.bitmap_updater.clone();
186+
tokio::task::spawn_blocking(move || time_warn!("Encoding bitmap", 10, updater.handle(&bitmap)))
187+
.await
188+
.unwrap()
189+
}
190+
}
191+
192+
#[derive(Debug, Default)]
193+
enum State {
194+
Start(DisplayUpdate),
195+
BitmapDiffs {
196+
diffs: Vec<Rect>,
197+
bitmap: BitmapUpdate,
198+
pos: usize,
199+
},
200+
#[default]
201+
Ended,
152202
}
153203

154204
#[cfg_attr(feature = "__bench", visibility::make(pub))]
155205
pub(crate) struct EncoderIter<'a> {
156206
encoder: &'a mut UpdateEncoder,
157-
update: Option<DisplayUpdate>,
207+
state: State,
158208
}
159209

160210
impl EncoderIter<'_> {
161211
#[cfg_attr(feature = "__bench", visibility::make(pub))]
162212
pub(crate) async fn next(&mut self) -> Option<Result<UpdateFragmenter>> {
163-
let update = self.update.take()?;
164-
let encoder = &mut self.encoder;
165-
166-
let res = match update {
167-
DisplayUpdate::Bitmap(bitmap) => encoder.bitmap(bitmap).await,
168-
DisplayUpdate::PointerPosition(pos) => encoder.pointer_position(pos),
169-
DisplayUpdate::RGBAPointer(ptr) => encoder.rgba_pointer(ptr),
170-
DisplayUpdate::ColorPointer(ptr) => encoder.color_pointer(ptr),
171-
DisplayUpdate::HidePointer => encoder.hide_pointer(),
172-
DisplayUpdate::DefaultPointer => encoder.default_pointer(),
173-
DisplayUpdate::Resize(_) => return None,
174-
};
175-
176-
Some(res)
213+
loop {
214+
let state = core::mem::take(&mut self.state);
215+
let encoder = &mut self.encoder;
216+
217+
let res = match state {
218+
State::Start(update) => match update {
219+
DisplayUpdate::Bitmap(bitmap) => {
220+
let diffs = encoder.bitmap_diffs(&bitmap);
221+
self.state = State::BitmapDiffs { diffs, bitmap, pos: 0 };
222+
continue;
223+
}
224+
DisplayUpdate::PointerPosition(pos) => encoder.pointer_position(pos),
225+
DisplayUpdate::RGBAPointer(ptr) => encoder.rgba_pointer(ptr),
226+
DisplayUpdate::ColorPointer(ptr) => encoder.color_pointer(ptr),
227+
DisplayUpdate::HidePointer => encoder.hide_pointer(),
228+
DisplayUpdate::DefaultPointer => encoder.default_pointer(),
229+
DisplayUpdate::Resize(_) => return None,
230+
},
231+
State::BitmapDiffs { diffs, bitmap, pos } => {
232+
let Some(rect) = diffs.get(pos) else {
233+
encoder.bitmap_update_framebuffer(bitmap, &diffs);
234+
self.state = State::Ended;
235+
return None;
236+
};
237+
let Rect { x, y, width, height } = *rect;
238+
let Some(sub) = bitmap.sub(
239+
u16::try_from(x).unwrap(),
240+
u16::try_from(y).unwrap(),
241+
NonZeroU16::new(u16::try_from(width).unwrap()).unwrap(),
242+
NonZeroU16::new(u16::try_from(height).unwrap()).unwrap(),
243+
) else {
244+
warn!("Failed to extract bitmap subregion");
245+
return None;
246+
};
247+
self.state = State::BitmapDiffs {
248+
diffs,
249+
bitmap,
250+
pos: pos + 1,
251+
};
252+
encoder.bitmap(sub).await
253+
}
254+
State::Ended => return None,
255+
};
256+
257+
return Some(res);
258+
}
177259
}
178260
}
179261

0 commit comments

Comments
 (0)