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,6 +57,7 @@ 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;
6162use ironrdp_pdu:: geometry:: { InclusiveRectangle , Rectangle as _} ;
6263use ironrdp_pdu:: { PduResult , decode_cursor, decode_err, pdu_other_err} ;
@@ -381,6 +382,7 @@ enum ClientState {
381382pub struct GraphicsPipelineClient {
382383 handler : Box < dyn GraphicsPipelineHandler > ,
383384 h264_decoder : Option < Box < dyn H264Decoder > > ,
385+ clearcodec_decoder : ClearCodecDecoder ,
384386
385387 decompressor : zgfx:: Decompressor ,
386388 decompressed_buffer : Vec < u8 > ,
@@ -399,10 +401,12 @@ impl GraphicsPipelineClient {
399401 /// Create a new `GraphicsPipelineClient`
400402 ///
401403 /// If `h264_decoder` is `None`, AVC420 frames are logged and skipped.
404+ /// ClearCodec decoding is always available (no external decoder required).
402405 pub fn new ( handler : Box < dyn GraphicsPipelineHandler > , h264_decoder : Option < Box < dyn H264Decoder > > ) -> Self {
403406 Self {
404407 handler,
405408 h264_decoder,
409+ clearcodec_decoder : ClearCodecDecoder :: new ( ) ,
406410 decompressor : zgfx:: Decompressor :: new ( ) ,
407411 decompressed_buffer : Vec :: new ( ) ,
408412 state : ClientState :: WaitingForConfirm ,
@@ -608,6 +612,9 @@ impl GraphicsPipelineClient {
608612 if let Some ( ref mut decoder) = self . h264_decoder {
609613 decoder. reset ( ) ;
610614 }
615+ // ClearCodec maintains V-bar and glyph caches that must be rebuilt
616+ // after a graphics reset (the server will re-send all needed state).
617+ self . clearcodec_decoder = ClearCodecDecoder :: new ( ) ;
611618
612619 debug ! ( width, height, "Graphics reset" ) ;
613620 self . handler . on_reset_graphics ( width, height) ;
@@ -694,6 +701,9 @@ impl GraphicsPipelineClient {
694701 debug ! ( "AVC444 codec not yet implemented, forwarding to handler" ) ;
695702 self . handler . on_unhandled_pdu ( & GfxPdu :: WireToSurface1 ( pdu) ) ;
696703 }
704+ Codec1Type :: ClearCodec => {
705+ self . decode_clearcodec ( pdu. surface_id , & pdu. destination_rectangle , & pdu. bitmap_data ) ?;
706+ }
697707 Codec1Type :: Uncompressed => {
698708 self . handle_uncompressed ( pdu) ;
699709 }
@@ -751,6 +761,36 @@ impl GraphicsPipelineClient {
751761 Ok ( ( ) )
752762 }
753763
764+ fn decode_clearcodec (
765+ & mut self ,
766+ surface_id : u16 ,
767+ dest_rect : & InclusiveRectangle ,
768+ bitmap_data : & [ u8 ] ,
769+ ) -> PduResult < ( ) > {
770+ let dest_width = dest_rect. width ( ) ;
771+ let dest_height = dest_rect. height ( ) ;
772+
773+ let bgra = self
774+ . clearcodec_decoder
775+ . decode ( bitmap_data, dest_width, dest_height)
776+ . map_err ( |e| pdu_other_err ! ( "ClearCodec decode" , source: e) ) ?;
777+
778+ // ClearCodec outputs BGRA; convert to RGBA for the uniform BitmapUpdate format
779+ let rgba = convert_bgra_to_rgba ( & bgra) ;
780+
781+ let update = BitmapUpdate {
782+ surface_id,
783+ destination_rectangle : dest_rect. clone ( ) ,
784+ codec_id : Codec1Type :: ClearCodec ,
785+ data : rgba,
786+ width : dest_width,
787+ height : dest_height,
788+ } ;
789+
790+ self . handler . on_bitmap_updated ( & update) ;
791+ Ok ( ( ) )
792+ }
793+
754794 fn handle_uncompressed ( & mut self , pdu : crate :: pdu:: WireToSurface1Pdu ) {
755795 let dest_width = pdu. destination_rectangle . width ( ) ;
756796 let dest_height = pdu. destination_rectangle . height ( ) ;
@@ -870,6 +910,19 @@ impl DvcClientProcessor for GraphicsPipelineClient {}
870910// Frame Cropping
871911// ============================================================================
872912
913+ /// Convert BGRA pixel data to RGBA8888
914+ ///
915+ /// ClearCodec produces BGRA output per [MS-RDPEGFX 2.2.4.1]. Reorder to
916+ /// [R, G, B, A] for the uniform `BitmapUpdate` pixel format.
917+ fn convert_bgra_to_rgba ( src : & [ u8 ] ) -> Vec < u8 > {
918+ debug_assert ! ( src. len( ) % 4 == 0 , "BGRA input length not aligned to 4 bytes" ) ;
919+ let mut dst = Vec :: with_capacity ( src. len ( ) ) ;
920+ for pixel in src. chunks_exact ( 4 ) {
921+ dst. extend_from_slice ( & [ pixel[ 2 ] , pixel[ 1 ] , pixel[ 0 ] , pixel[ 3 ] ] ) ;
922+ }
923+ dst
924+ }
925+
873926/// Convert uncompressed 32bpp little-endian pixels to RGBA8888
874927///
875928/// The wire format for uncompressed graphics is 0xAARRGGBB in a 32-bit
@@ -1031,6 +1084,24 @@ mod tests {
10311084 assert_eq ! ( cropped. len( ) , 1920 * 1080 * 4 ) ;
10321085 }
10331086
1087+ #[ test]
1088+ fn convert_bgra_to_rgba_reorders_channels ( ) {
1089+ // BGRA input: [B, G, R, A] per pixel
1090+ let bgra = vec ! [
1091+ 0xFF , 0x00 , 0x00 , 0xCC , // B=255, G=0, R=0, A=204 (blue)
1092+ 0x00 , 0xFF , 0x00 , 0x80 , // B=0, G=255, R=0, A=128 (green)
1093+ ] ;
1094+ let rgba = convert_bgra_to_rgba ( & bgra) ;
1095+ // Expected: [R, G, B, A] per pixel
1096+ assert_eq ! (
1097+ rgba,
1098+ vec![
1099+ 0x00 , 0x00 , 0xFF , 0xCC , // R=0, G=0, B=255, A=204
1100+ 0x00 , 0xFF , 0x00 , 0x80 , // R=0, G=255, B=0, A=128
1101+ ]
1102+ ) ;
1103+ }
1104+
10341105 #[ test]
10351106 fn convert_uncompressed_bgrx_to_rgba ( ) {
10361107 // Wire format: [B, G, R, A] per pixel (0xAARRGGBB little-endian)
0 commit comments