Skip to content

Commit fb3769c

Browse files
elmarcoCBenoit
authored andcommitted
feat(server): find & send the damaged tiles
Keep a framebuffer and tile-diff against it, to save from encoding/sending the same bitmap data regions. Signed-off-by: Marc-André Lureau <[email protected]>
1 parent 20581bb commit fb3769c

File tree

2 files changed

+211
-27
lines changed

2 files changed

+211
-27
lines changed

crates/ironrdp-server/src/display.rs

Lines changed: 103 additions & 1 deletion
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]
@@ -74,12 +75,73 @@ impl TryInto<Framebuffer> for BitmapUpdate {
7475
width: self.width,
7576
height: self.height,
7677
format: self.format,
77-
data: self.data.try_into_mut().map_err(|_| "BitmapUpdate is shared")?,
78+
data: self.data.into(),
7879
stride: self.stride,
7980
})
8081
}
8182
}
8283

84+
impl Framebuffer {
85+
pub fn new(width: NonZeroU16, height: NonZeroU16, format: PixelFormat) -> Self {
86+
let mut data = BytesMut::new();
87+
let w = usize::from(width.get());
88+
let h = usize::from(height.get());
89+
let bpp = usize::from(format.bytes_per_pixel());
90+
data.resize(bpp * w * h, 0);
91+
92+
Self {
93+
width,
94+
height,
95+
format,
96+
data,
97+
stride: bpp * w,
98+
}
99+
}
100+
101+
pub fn update(&mut self, bitmap: &BitmapUpdate) {
102+
if self.format != bitmap.format {
103+
warn!("Bitmap format mismatch, unsupported");
104+
return;
105+
}
106+
let bpp = usize::from(self.format.bytes_per_pixel());
107+
let x = usize::from(bitmap.x);
108+
let y = usize::from(bitmap.y);
109+
let width = usize::from(bitmap.width.get());
110+
let height = usize::from(bitmap.height.get());
111+
112+
let data = &mut self.data;
113+
let start = y * self.stride + x * bpp;
114+
let end = start + (height - 1) * self.stride + width * bpp;
115+
let dst = &mut data[start..end];
116+
117+
for y in 0..height {
118+
let start = y * bitmap.stride;
119+
let end = start + width * bpp;
120+
let src = bitmap.data.slice(start..end);
121+
122+
let start = y * self.stride;
123+
let end = start + width * bpp;
124+
let dst = &mut dst[start..end];
125+
126+
dst.copy_from_slice(&src);
127+
}
128+
}
129+
130+
pub(crate) fn update_diffs(&mut self, bitmap: &BitmapUpdate, diffs: &[diff::Rect]) {
131+
diffs
132+
.iter()
133+
.filter_map(|diff| {
134+
let x = u16::try_from(diff.x).ok()?;
135+
let y = u16::try_from(diff.y).ok()?;
136+
let width = u16::try_from(diff.width).ok().and_then(NonZeroU16::new)?;
137+
let height = u16::try_from(diff.height).ok().and_then(NonZeroU16::new)?;
138+
139+
bitmap.sub(x, y, width, height)
140+
})
141+
.for_each(|sub| self.update(&sub));
142+
}
143+
}
144+
83145
/// Bitmap Display Update
84146
///
85147
/// Bitmap updates are encoded using RDP 6.0 compression, fragmented and sent using
@@ -231,3 +293,43 @@ pub trait RdpServerDisplay: Send {
231293
debug!(?layout, "Requesting layout")
232294
}
233295
}
296+
297+
#[cfg(test)]
298+
mod tests {
299+
use super::{BitmapUpdate, Framebuffer};
300+
use core::num::NonZeroU16;
301+
use ironrdp_graphics::{diff::Rect, image_processing::PixelFormat};
302+
303+
#[test]
304+
fn framebuffer_update() {
305+
let width = NonZeroU16::new(800).unwrap();
306+
let height = NonZeroU16::new(600).unwrap();
307+
let fmt = PixelFormat::ABgr32;
308+
let bpp = usize::from(fmt.bytes_per_pixel());
309+
let mut fb = Framebuffer::new(width, height, fmt);
310+
311+
let width = 15;
312+
let stride = width * bpp;
313+
let height = 20;
314+
let data = vec![1u8; height * stride];
315+
let update = BitmapUpdate {
316+
x: 1,
317+
y: 2,
318+
width: NonZeroU16::new(15).unwrap(),
319+
height: NonZeroU16::new(20).unwrap(),
320+
format: fmt,
321+
data: data.into(),
322+
stride,
323+
};
324+
let diffs = vec![Rect::new(2, 3, 4, 5)];
325+
fb.update_diffs(&update, &diffs);
326+
let data = fb.data;
327+
for y in 5..10 {
328+
for x in 3..7 {
329+
for b in 0..bpp {
330+
assert_eq!(data[y * fb.stride + x * bpp + b], 1);
331+
}
332+
}
333+
}
334+
}
335+
}

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

Lines changed: 108 additions & 26 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;
@@ -29,7 +31,6 @@ enum CodecId {
2931

3032
pub(crate) struct UpdateEncoder {
3133
desktop_size: DesktopSize,
32-
// FIXME: draw updates on the framebuffer
3334
framebuffer: Option<Framebuffer>,
3435
bitmap_updater: BitmapUpdater,
3536
}
@@ -63,7 +64,7 @@ impl UpdateEncoder {
6364
pub(crate) fn update(&mut self, update: DisplayUpdate) -> EncoderIter<'_> {
6465
EncoderIter {
6566
encoder: self,
66-
update: Some(update),
67+
state: State::Start(update),
6768
}
6869
}
6970

@@ -122,14 +123,41 @@ impl UpdateEncoder {
122123
Ok(UpdateFragmenter::new(UpdateCode::PositionPointer, encode_vec(&pos)?))
123124
}
124125

125-
async fn bitmap(&mut self, bitmap: BitmapUpdate) -> Result<UpdateFragmenter> {
126-
// Clone to satisfy spawn_blocking 'static requirement
127-
// this should be cheap, even if using bitmap, since vec![] will be empty
128-
let mut updater = self.bitmap_updater.clone();
129-
let (res, bitmap) =
130-
tokio::task::spawn_blocking(move || time_warn!("Encoding bitmap", 10, (updater.handle(&bitmap), bitmap)))
131-
.await
132-
.unwrap();
126+
fn bitmap_diffs(&mut self, bitmap: &BitmapUpdate) -> Vec<Rect> {
127+
// TODO: we may want to make it optional for servers that already provide damaged regions
128+
const USE_DIFFS: bool = true;
129+
130+
if let Some(Framebuffer {
131+
data,
132+
stride,
133+
width,
134+
height,
135+
..
136+
}) = USE_DIFFS.then_some(self.framebuffer.as_ref()).flatten()
137+
{
138+
find_different_rects_sub::<4>(
139+
data,
140+
*stride,
141+
width.get().into(),
142+
height.get().into(),
143+
&bitmap.data,
144+
bitmap.stride,
145+
bitmap.width.get().into(),
146+
bitmap.height.get().into(),
147+
bitmap.x.into(),
148+
bitmap.y.into(),
149+
)
150+
} else {
151+
vec![Rect {
152+
x: 0,
153+
y: 0,
154+
width: bitmap.width.get().into(),
155+
height: bitmap.height.get().into(),
156+
}]
157+
}
158+
}
159+
160+
fn bitmap_update_framebuffer(&mut self, bitmap: BitmapUpdate, diffs: &[Rect]) {
133161
if bitmap.x == 0
134162
&& bitmap.y == 0
135163
&& bitmap.width.get() == self.desktop_size.width
@@ -139,32 +167,86 @@ impl UpdateEncoder {
139167
Ok(framebuffer) => self.framebuffer = Some(framebuffer),
140168
Err(err) => warn!("Failed to convert bitmap to framebuffer: {}", err),
141169
}
170+
} else if let Some(fb) = self.framebuffer.as_mut() {
171+
fb.update_diffs(&bitmap, diffs);
142172
}
143-
res
144173
}
174+
175+
async fn bitmap(&mut self, bitmap: BitmapUpdate) -> Result<UpdateFragmenter> {
176+
// Clone to satisfy spawn_blocking 'static requirement
177+
// this should be cheap, even if using bitmap, since vec![] will be empty
178+
let mut updater = self.bitmap_updater.clone();
179+
tokio::task::spawn_blocking(move || time_warn!("Encoding bitmap", 10, updater.handle(&bitmap)))
180+
.await
181+
.unwrap()
182+
}
183+
}
184+
185+
#[derive(Debug, Default)]
186+
enum State {
187+
Start(DisplayUpdate),
188+
BitmapDiffs {
189+
diffs: Vec<Rect>,
190+
bitmap: BitmapUpdate,
191+
pos: usize,
192+
},
193+
#[default]
194+
Ended,
145195
}
146196

147197
pub(crate) struct EncoderIter<'a> {
148198
encoder: &'a mut UpdateEncoder,
149-
update: Option<DisplayUpdate>,
199+
state: State,
150200
}
151201

152202
impl EncoderIter<'_> {
153203
pub(crate) async fn next(&mut self) -> Option<Result<UpdateFragmenter>> {
154-
let update = self.update.take()?;
155-
let encoder = &mut self.encoder;
156-
157-
let res = match update {
158-
DisplayUpdate::Bitmap(bitmap) => encoder.bitmap(bitmap).await,
159-
DisplayUpdate::PointerPosition(pos) => UpdateEncoder::pointer_position(pos),
160-
DisplayUpdate::RGBAPointer(ptr) => UpdateEncoder::rgba_pointer(ptr),
161-
DisplayUpdate::ColorPointer(ptr) => UpdateEncoder::color_pointer(ptr),
162-
DisplayUpdate::HidePointer => UpdateEncoder::hide_pointer(),
163-
DisplayUpdate::DefaultPointer => UpdateEncoder::default_pointer(),
164-
DisplayUpdate::Resize(_) => return None,
165-
};
166-
167-
Some(res)
204+
loop {
205+
let state = core::mem::take(&mut self.state);
206+
let encoder = &mut self.encoder;
207+
208+
let res = match state {
209+
State::Start(update) => match update {
210+
DisplayUpdate::Bitmap(bitmap) => {
211+
let diffs = encoder.bitmap_diffs(&bitmap);
212+
self.state = State::BitmapDiffs { diffs, bitmap, pos: 0 };
213+
continue;
214+
}
215+
DisplayUpdate::PointerPosition(pos) => UpdateEncoder::pointer_position(pos),
216+
DisplayUpdate::RGBAPointer(ptr) => UpdateEncoder::rgba_pointer(ptr),
217+
DisplayUpdate::ColorPointer(ptr) => UpdateEncoder::color_pointer(ptr),
218+
DisplayUpdate::HidePointer => UpdateEncoder::hide_pointer(),
219+
DisplayUpdate::DefaultPointer => UpdateEncoder::default_pointer(),
220+
DisplayUpdate::Resize(_) => return None,
221+
},
222+
State::BitmapDiffs { diffs, bitmap, pos } => {
223+
let Some(rect) = diffs.get(pos) else {
224+
encoder.bitmap_update_framebuffer(bitmap, &diffs);
225+
self.state = State::Ended;
226+
return None;
227+
};
228+
let Rect { x, y, width, height } = *rect;
229+
let Some(sub) = bitmap.sub(
230+
u16::try_from(x).unwrap(),
231+
u16::try_from(y).unwrap(),
232+
NonZeroU16::new(u16::try_from(width).unwrap()).unwrap(),
233+
NonZeroU16::new(u16::try_from(height).unwrap()).unwrap(),
234+
) else {
235+
warn!("Failed to extract bitmap subregion");
236+
return None;
237+
};
238+
self.state = State::BitmapDiffs {
239+
diffs,
240+
bitmap,
241+
pos: pos + 1,
242+
};
243+
encoder.bitmap(sub).await
244+
}
245+
State::Ended => return None,
246+
};
247+
248+
return Some(res);
249+
}
168250
}
169251
}
170252

0 commit comments

Comments
 (0)