Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions crates/ironrdp-egfx/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,52 @@ impl GraphicsPipelineServer {
Some(frame_id)
}

/// Queue a ClearCodec frame for transmission.
///
/// ClearCodec is a mandatory lossless codec for all EGFX versions. It
/// provides excellent compression for text, UI elements, and icons.
///
/// `bitmap_data` should be a pre-encoded ClearCodec bitmap stream
/// (as produced by `ironrdp_graphics::clearcodec::ClearCodecEncoder`).
///
/// Returns `Some(frame_id)` if queued, `None` if backpressure is active
/// or the server is not ready.
pub fn send_clearcodec_frame(
&mut self,
surface_id: u16,
destination_rectangle: InclusiveRectangle,
bitmap_data: Vec<u8>,
timestamp_ms: u32,
) -> Option<u32> {
if !self.is_ready() {
return None;
}
if self.should_backpressure() {
self.qoe.record_backpressure();
return None;
}

let surface = self.surfaces.get(surface_id)?;

let timestamp = Self::make_timestamp(timestamp_ms);
let frame_id = self.frames.begin_frame(timestamp);

self.output_queue
.push_back(GfxPdu::StartFrame(StartFramePdu { timestamp, frame_id }));

self.output_queue.push_back(GfxPdu::WireToSurface1(WireToSurface1Pdu {
surface_id,
codec_id: Codec1Type::ClearCodec,
pixel_format: surface.pixel_format,
destination_rectangle,
bitmap_data,
}));

self.output_queue.push_back(GfxPdu::EndFrame(EndFramePdu { frame_id }));

Some(frame_id)
}

// ========================================================================
// Output Management
// ========================================================================
Expand Down
98 changes: 98 additions & 0 deletions crates/ironrdp-graphics/src/clearcodec/glyph_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Glyph cache for ClearCodec (MS-RDPEGFX 2.2.4.1).
//!
//! When a bitmap area is <= 1024 pixels, ClearCodec can index it in a
//! 4,000-entry glyph cache. On a cache hit (FLAG_GLYPH_HIT), the previously
//! cached pixel data is reused without retransmission.

/// Maximum number of glyph cache entries.
pub const GLYPH_CACHE_SIZE: usize = 4_000;

/// A cached glyph entry: BGRA pixel data with dimensions.
#[derive(Debug, Clone)]
pub struct GlyphEntry {
pub width: u16,
pub height: u16,
/// BGRA pixel data (4 bytes per pixel).
pub pixels: Vec<u8>,
}

/// Glyph cache for ClearCodec bitmap deduplication.
pub struct GlyphCache {
entries: Vec<Option<GlyphEntry>>,
}

impl GlyphCache {
pub fn new() -> Self {
let mut entries = Vec::with_capacity(GLYPH_CACHE_SIZE);
entries.resize_with(GLYPH_CACHE_SIZE, || None);
Self { entries }
}

/// Look up a glyph by its cache index.
pub fn get(&self, index: u16) -> Option<&GlyphEntry> {
self.entries.get(usize::from(index)).and_then(|slot| slot.as_ref())
}

/// Store a glyph at the given index.
///
/// Returns `true` if the index was valid and the entry was stored.
pub fn store(&mut self, index: u16, entry: GlyphEntry) -> bool {
let idx = usize::from(index);
if idx < GLYPH_CACHE_SIZE {
self.entries[idx] = Some(entry);
true
} else {
false
}
}

/// Reset the entire glyph cache, removing all entries.
pub fn reset(&mut self) {
for slot in &mut self.entries {
*slot = None;
}
}
}

impl Default for GlyphCache {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn store_and_retrieve() {
let mut cache = GlyphCache::new();
let entry = GlyphEntry {
width: 8,
height: 16,
pixels: vec![0xFF; 8 * 16 * 4],
};
assert!(cache.store(42, entry));
let retrieved = cache.get(42).unwrap();
assert_eq!(retrieved.width, 8);
assert_eq!(retrieved.height, 16);
}

#[test]
fn get_empty_returns_none() {
let cache = GlyphCache::new();
assert!(cache.get(0).is_none());
assert!(cache.get(3999).is_none());
}

#[test]
fn reject_out_of_range() {
let mut cache = GlyphCache::new();
let entry = GlyphEntry {
width: 1,
height: 1,
pixels: vec![0; 4],
};
assert!(!cache.store(4000, entry));
}
}
Loading
Loading