11//! Client-side EGFX implementation
22//!
33//! This module provides client-side support for the Graphics Pipeline Extension
4- //! ([MS-RDPEGFX]), including H.264 AVC420 decode and surface management.
4+ //! ([MS-RDPEGFX]), including H.264 AVC420 decode, ClearCodec decode, and surface management.
55//!
66//! # Protocol Compliance
77//!
2424//! | |
2525//! | (For each frame:) |
2626//! |--- StartFrame ----------------------->|
27- //! |--- WireToSurface1 (H.264 ) ----------->| -> H264Decoder:: decode()
27+ //! |--- WireToSurface1 (codec ) ----------->| -> H264/ClearCodec decode
2828//! |--- EndFrame ------------------------->| -> FrameAcknowledge
2929//! | |
3030//! |<---------- FrameAcknowledge ----------|
@@ -57,8 +57,9 @@ use std::collections::BTreeMap;
5757
5858use ironrdp_core:: { Decode as _, ReadCursor , impl_as_any} ;
5959use ironrdp_dvc:: { DvcClientProcessor , DvcMessage , DvcProcessor } ;
60+ use ironrdp_graphics:: clearcodec:: ClearCodecDecoder ;
6061use ironrdp_graphics:: zgfx;
61- use ironrdp_pdu:: geometry:: { ExclusiveRectangle , Rectangle as _ } ;
62+ use ironrdp_pdu:: geometry:: ExclusiveRectangle ;
6263use ironrdp_pdu:: { PduResult , decode_cursor, decode_err, pdu_other_err} ;
6364use tracing:: { debug, trace, warn} ;
6465
@@ -383,6 +384,7 @@ enum ClientState {
383384pub struct GraphicsPipelineClient {
384385 handler : Box < dyn GraphicsPipelineHandler > ,
385386 h264_decoder : Option < Box < dyn H264Decoder > > ,
387+ clearcodec_decoder : ClearCodecDecoder ,
386388
387389 decompressor : zgfx:: Decompressor ,
388390 decompressed_buffer : Vec < u8 > ,
@@ -401,10 +403,12 @@ impl GraphicsPipelineClient {
401403 /// Create a new `GraphicsPipelineClient`
402404 ///
403405 /// If `h264_decoder` is `None`, AVC420 frames are logged and skipped.
406+ /// ClearCodec decoding is always available (no external decoder required).
404407 pub fn new ( handler : Box < dyn GraphicsPipelineHandler > , h264_decoder : Option < Box < dyn H264Decoder > > ) -> Self {
405408 Self {
406409 handler,
407410 h264_decoder,
411+ clearcodec_decoder : ClearCodecDecoder :: new ( ) ,
408412 decompressor : zgfx:: Decompressor :: new ( ) ,
409413 decompressed_buffer : Vec :: new ( ) ,
410414 state : ClientState :: WaitingForConfirm ,
@@ -632,6 +636,9 @@ impl GraphicsPipelineClient {
632636 if let Some ( ref mut decoder) = self . h264_decoder {
633637 decoder. reset ( ) ;
634638 }
639+ // ClearCodec maintains V-bar and glyph caches that must be rebuilt
640+ // after a graphics reset (the server will re-send all needed state).
641+ self . clearcodec_decoder = ClearCodecDecoder :: new ( ) ;
635642
636643 debug ! ( width, height, "Graphics reset" ) ;
637644 self . handler . on_reset_graphics ( width, height) ;
@@ -720,6 +727,9 @@ impl GraphicsPipelineClient {
720727 debug ! ( "AVC444 codec not yet implemented, forwarding to handler" ) ;
721728 self . handler . on_unhandled_pdu ( & GfxPdu :: WireToSurface1 ( pdu) ) ;
722729 }
730+ Codec1Type :: ClearCodec => {
731+ self . decode_clearcodec ( pdu. surface_id , & pdu. destination_rectangle , & pdu. bitmap_data ) ?;
732+ }
723733 Codec1Type :: Uncompressed => {
724734 self . handle_uncompressed ( pdu) ;
725735 }
@@ -745,8 +755,10 @@ impl GraphicsPipelineClient {
745755 . decode ( stream. data )
746756 . map_err ( |e| pdu_other_err ! ( "H.264 decode" , source: e) ) ?;
747757
748- let dest_width = dest_rect. width ( ) ;
749- let dest_height = dest_rect. height ( ) ;
758+ // MS-RDPEGFX 2.2.1.4.1: RDPGFX_RECT16 right/bottom are exclusive (one-past-end),
759+ // so dimensions are right-left / bottom-top despite the parsed type's name.
760+ let dest_width = dest_rect. right - dest_rect. left ;
761+ let dest_height = dest_rect. bottom - dest_rect. top ;
750762
751763 // Decoded frame must be at least as large as the destination rectangle.
752764 // Larger is expected (macroblock alignment) and handled by cropping.
@@ -777,9 +789,41 @@ impl GraphicsPipelineClient {
777789 Ok ( ( ) )
778790 }
779791
792+ fn decode_clearcodec (
793+ & mut self ,
794+ surface_id : u16 ,
795+ dest_rect : & ExclusiveRectangle ,
796+ bitmap_data : & [ u8 ] ,
797+ ) -> PduResult < ( ) > {
798+ // MS-RDPEGFX 2.2.1.4.1: see decode_avc420 above for wire-format note.
799+ let dest_width = dest_rect. right - dest_rect. left ;
800+ let dest_height = dest_rect. bottom - dest_rect. top ;
801+
802+ let bgra = self
803+ . clearcodec_decoder
804+ . decode ( bitmap_data, dest_width, dest_height)
805+ . map_err ( |e| pdu_other_err ! ( "ClearCodec decode" , source: e) ) ?;
806+
807+ // ClearCodec outputs BGRA; convert to RGBA for the uniform BitmapUpdate format
808+ let rgba = convert_bgra_to_rgba ( & bgra) ;
809+
810+ let update = BitmapUpdate {
811+ surface_id,
812+ destination_rectangle : dest_rect. clone ( ) ,
813+ codec_id : Codec1Type :: ClearCodec ,
814+ data : rgba,
815+ width : dest_width,
816+ height : dest_height,
817+ } ;
818+
819+ self . handler . on_bitmap_updated ( & update) ;
820+ Ok ( ( ) )
821+ }
822+
780823 fn handle_uncompressed ( & mut self , pdu : crate :: pdu:: WireToSurface1Pdu ) {
781- let dest_width = pdu. destination_rectangle . width ( ) ;
782- let dest_height = pdu. destination_rectangle . height ( ) ;
824+ // MS-RDPEGFX 2.2.1.4.1: see decode_avc420 above for wire-format note.
825+ let dest_width = pdu. destination_rectangle . right - pdu. destination_rectangle . left ;
826+ let dest_height = pdu. destination_rectangle . bottom - pdu. destination_rectangle . top ;
783827
784828 // Convert wire-format pixels to RGBA.
785829 // BitmapUpdate.data is always RGBA8888 regardless of codec -- this is
@@ -807,7 +851,9 @@ impl GraphicsPipelineClient {
807851
808852 self . handler . on_frame_complete ( frame_id) ;
809853
810- // Per [3.3.5.12]: client MUST send FrameAcknowledge after EndFrame
854+ // Per [3.3.5.12]: client MUST send FrameAcknowledge after EndFrame.
855+ // We send the actual queue depth (not Unavailable / 0xFFFFFFFF as FreeRDP does);
856+ // the real value gives the server backpressure information for frame pacing.
811857 let ack = GfxPdu :: FrameAcknowledge ( FrameAcknowledgePdu {
812858 queue_depth : QueueDepth :: from_u32 ( self . frames_queued ) ,
813859 frame_id,
@@ -896,6 +942,19 @@ impl DvcClientProcessor for GraphicsPipelineClient {}
896942// Frame Cropping
897943// ============================================================================
898944
945+ /// Convert BGRA pixel data to RGBA8888
946+ ///
947+ /// ClearCodec produces BGRA output per [MS-RDPEGFX 2.2.4.1]. Reorder to
948+ /// [R, G, B, A] for the uniform `BitmapUpdate` pixel format.
949+ fn convert_bgra_to_rgba ( src : & [ u8 ] ) -> Vec < u8 > {
950+ debug_assert ! ( src. len( ) % 4 == 0 , "BGRA input length not aligned to 4 bytes" ) ;
951+ let mut dst = Vec :: with_capacity ( src. len ( ) ) ;
952+ for pixel in src. chunks_exact ( 4 ) {
953+ dst. extend_from_slice ( & [ pixel[ 2 ] , pixel[ 1 ] , pixel[ 0 ] , pixel[ 3 ] ] ) ;
954+ }
955+ dst
956+ }
957+
899958/// Convert uncompressed 32bpp little-endian pixels to RGBA8888
900959///
901960/// The wire format for uncompressed graphics is 0xAARRGGBB in a 32-bit
@@ -1057,6 +1116,24 @@ mod tests {
10571116 assert_eq ! ( cropped. len( ) , 1920 * 1080 * 4 ) ;
10581117 }
10591118
1119+ #[ test]
1120+ fn convert_bgra_to_rgba_reorders_channels ( ) {
1121+ // BGRA input: [B, G, R, A] per pixel
1122+ let bgra = vec ! [
1123+ 0xFF , 0x00 , 0x00 , 0xCC , // B=255, G=0, R=0, A=204 (blue)
1124+ 0x00 , 0xFF , 0x00 , 0x80 , // B=0, G=255, R=0, A=128 (green)
1125+ ] ;
1126+ let rgba = convert_bgra_to_rgba ( & bgra) ;
1127+ // Expected: [R, G, B, A] per pixel
1128+ assert_eq ! (
1129+ rgba,
1130+ vec![
1131+ 0x00 , 0x00 , 0xFF , 0xCC , // R=0, G=0, B=255, A=204
1132+ 0x00 , 0xFF , 0x00 , 0x80 , // R=0, G=255, B=0, A=128
1133+ ]
1134+ ) ;
1135+ }
1136+
10601137 #[ test]
10611138 fn convert_uncompressed_bgrx_to_rgba ( ) {
10621139 // Wire format: [B, G, R, A] per pixel (0xAARRGGBB little-endian)
0 commit comments