Skip to content

Commit a22a842

Browse files
committed
feat(graphics,egfx): add progressive RFX server encode and mixed-codec frames
Add server-side progressive encode pipeline and multi-codec frame support: ironrdp-graphics: Progressive RFX encoder producing first-pass and upgrade-pass tile data via forward DWT + SRL quantization. Quality level control for adaptive bandwidth usage. ironrdp-egfx: send_remotefx_progressive_frame() for WireToSurface2 progressive transmission. MixedTilePayload enum and send_mixed_frame() for combining ClearCodec + Progressive RFX tiles in a single StartFrame/EndFrame pair.
1 parent a108923 commit a22a842

2 files changed

Lines changed: 506 additions & 1 deletion

File tree

crates/ironrdp-egfx/src/server.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,31 @@ pub struct GraphicsPipelineServer {
864864
compression_mode: CompressionMode,
865865
}
866866

867+
/// Payload for a single tile within a mixed-codec frame.
868+
///
869+
/// Each variant corresponds to a different EGFX codec. Used with
870+
/// [`GraphicsPipelineServer::send_mixed_frame()`] to pack multiple codec
871+
/// types into a single `StartFrame`/`EndFrame` pair.
872+
pub enum MixedTilePayload {
873+
/// Lossless ClearCodec tile (text, UI elements, icons).
874+
/// `bitmap_data` is a pre-encoded ClearCodec bitmap stream.
875+
ClearCodec {
876+
destination: InclusiveRectangle,
877+
bitmap_data: Vec<u8>,
878+
},
879+
/// RemoteFX Progressive tile (photos, gradients).
880+
/// `progressive_data` is a valid progressive block stream.
881+
RemoteFxProgressive {
882+
codec_context_id: u32,
883+
progressive_data: Vec<u8>,
884+
},
885+
/// H.264 AVC420 tile (video, high-motion content).
886+
Avc420 {
887+
regions: Vec<Avc420Region>,
888+
h264_data: Vec<u8>,
889+
},
890+
}
891+
867892
impl GraphicsPipelineServer {
868893
/// Create a new GraphicsPipelineServer
869894
pub fn new(handler: Box<dyn GraphicsPipelineHandler>) -> Self {
@@ -1455,6 +1480,91 @@ impl GraphicsPipelineServer {
14551480
Some(frame_id)
14561481
}
14571482

1483+
// ========================================================================
1484+
// Mixed-Codec Frame Support
1485+
// ========================================================================
1486+
1487+
/// Queue a mixed-codec frame containing tiles encoded with different codecs.
1488+
///
1489+
/// This is the core of multi-codec EGFX: a single frame update can contain
1490+
/// ClearCodec tiles (lossless text), Progressive tiles (photos), and H.264
1491+
/// tiles (video), all sent between one `StartFrame`/`EndFrame` pair.
1492+
///
1493+
/// This matches how Azure VDI achieves its visual quality — each tile uses
1494+
/// the codec best suited to its content type.
1495+
///
1496+
/// Returns `Some(frame_id)` if queued, `None` if not ready or backpressured.
1497+
pub fn send_mixed_frame(
1498+
&mut self,
1499+
surface_id: u16,
1500+
tiles: Vec<MixedTilePayload>,
1501+
timestamp_ms: u32,
1502+
) -> Option<u32> {
1503+
if !self.is_ready() {
1504+
return None;
1505+
}
1506+
if self.should_backpressure() {
1507+
return None;
1508+
}
1509+
if tiles.is_empty() {
1510+
return None;
1511+
}
1512+
1513+
let surface = self.surfaces.get(surface_id)?;
1514+
let pixel_format = surface.pixel_format;
1515+
1516+
let timestamp = Self::make_timestamp(timestamp_ms);
1517+
let frame_id = self.frames.begin_frame(timestamp);
1518+
1519+
self.output_queue
1520+
.push_back(GfxPdu::StartFrame(StartFramePdu { timestamp, frame_id }));
1521+
1522+
for tile in tiles {
1523+
match tile {
1524+
MixedTilePayload::ClearCodec {
1525+
destination,
1526+
bitmap_data,
1527+
} => {
1528+
self.output_queue.push_back(GfxPdu::WireToSurface1(WireToSurface1Pdu {
1529+
surface_id,
1530+
codec_id: Codec1Type::ClearCodec,
1531+
pixel_format,
1532+
destination_rectangle: destination,
1533+
bitmap_data,
1534+
}));
1535+
}
1536+
MixedTilePayload::RemoteFxProgressive {
1537+
codec_context_id,
1538+
progressive_data,
1539+
} => {
1540+
self.output_queue.push_back(GfxPdu::WireToSurface2(WireToSurface2Pdu {
1541+
surface_id,
1542+
codec_id: Codec2Type::RemoteFxProgressive,
1543+
codec_context_id,
1544+
pixel_format,
1545+
bitmap_data: progressive_data,
1546+
}));
1547+
}
1548+
MixedTilePayload::Avc420 { regions, h264_data } => {
1549+
let encoded_stream = encode_avc420_bitmap_stream(&regions, &h264_data);
1550+
let target_rect = Self::compute_dest_rect(&regions, surface.width, surface.height);
1551+
1552+
self.output_queue.push_back(GfxPdu::WireToSurface1(WireToSurface1Pdu {
1553+
surface_id,
1554+
codec_id: Codec1Type::Avc420,
1555+
pixel_format,
1556+
destination_rectangle: target_rect,
1557+
bitmap_data: encoded_stream,
1558+
}));
1559+
}
1560+
}
1561+
}
1562+
1563+
self.output_queue.push_back(GfxPdu::EndFrame(EndFramePdu { frame_id }));
1564+
1565+
Some(frame_id)
1566+
}
1567+
14581568
// ========================================================================
14591569
// Output Management
14601570
// ========================================================================

0 commit comments

Comments
 (0)