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:: { InclusiveRectangle , Rectangle as _ } ;
62+ use ironrdp_pdu:: geometry:: InclusiveRectangle ;
6263use ironrdp_pdu:: { PduResult , decode_cursor, decode_err, pdu_other_err} ;
6364use tracing:: { debug, trace, warn} ;
6465
@@ -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 }
@@ -719,8 +729,10 @@ impl GraphicsPipelineClient {
719729 . decode ( stream. data )
720730 . map_err ( |e| pdu_other_err ! ( "H.264 decode" , source: e) ) ?;
721731
722- let dest_width = dest_rect. width ( ) ;
723- let dest_height = dest_rect. height ( ) ;
732+ // MS-RDPEGFX 2.2.1.4.1: RDPGFX_RECT16 right/bottom are exclusive (one-past-end),
733+ // so dimensions are right-left / bottom-top despite the parsed type's name.
734+ let dest_width = dest_rect. right - dest_rect. left ;
735+ let dest_height = dest_rect. bottom - dest_rect. top ;
724736
725737 // Decoded frame must be at least as large as the destination rectangle.
726738 // Larger is expected (macroblock alignment) and handled by cropping.
@@ -751,9 +763,41 @@ impl GraphicsPipelineClient {
751763 Ok ( ( ) )
752764 }
753765
766+ fn decode_clearcodec (
767+ & mut self ,
768+ surface_id : u16 ,
769+ dest_rect : & InclusiveRectangle ,
770+ bitmap_data : & [ u8 ] ,
771+ ) -> PduResult < ( ) > {
772+ // MS-RDPEGFX 2.2.1.4.1: see decode_avc420 above for wire-format note.
773+ let dest_width = dest_rect. right - dest_rect. left ;
774+ let dest_height = dest_rect. bottom - dest_rect. top ;
775+
776+ let bgra = self
777+ . clearcodec_decoder
778+ . decode ( bitmap_data, dest_width, dest_height)
779+ . map_err ( |e| pdu_other_err ! ( "ClearCodec decode" , source: e) ) ?;
780+
781+ // ClearCodec outputs BGRA; convert to RGBA for the uniform BitmapUpdate format
782+ let rgba = convert_bgra_to_rgba ( & bgra) ;
783+
784+ let update = BitmapUpdate {
785+ surface_id,
786+ destination_rectangle : dest_rect. clone ( ) ,
787+ codec_id : Codec1Type :: ClearCodec ,
788+ data : rgba,
789+ width : dest_width,
790+ height : dest_height,
791+ } ;
792+
793+ self . handler . on_bitmap_updated ( & update) ;
794+ Ok ( ( ) )
795+ }
796+
754797 fn handle_uncompressed ( & mut self , pdu : crate :: pdu:: WireToSurface1Pdu ) {
755- let dest_width = pdu. destination_rectangle . width ( ) ;
756- let dest_height = pdu. destination_rectangle . height ( ) ;
798+ // MS-RDPEGFX 2.2.1.4.1: see decode_avc420 above for wire-format note.
799+ let dest_width = pdu. destination_rectangle . right - pdu. destination_rectangle . left ;
800+ let dest_height = pdu. destination_rectangle . bottom - pdu. destination_rectangle . top ;
757801
758802 // Convert wire-format pixels to RGBA.
759803 // BitmapUpdate.data is always RGBA8888 regardless of codec -- this is
@@ -781,7 +825,9 @@ impl GraphicsPipelineClient {
781825
782826 self . handler . on_frame_complete ( frame_id) ;
783827
784- // Per [3.3.5.12]: client MUST send FrameAcknowledge after EndFrame
828+ // Per [3.3.5.12]: client MUST send FrameAcknowledge after EndFrame.
829+ // We send the actual queue depth (not Unavailable / 0xFFFFFFFF as FreeRDP does);
830+ // the real value gives the server backpressure information for frame pacing.
785831 let ack = GfxPdu :: FrameAcknowledge ( FrameAcknowledgePdu {
786832 queue_depth : QueueDepth :: from_u32 ( self . frames_queued ) ,
787833 frame_id,
@@ -870,6 +916,19 @@ impl DvcClientProcessor for GraphicsPipelineClient {}
870916// Frame Cropping
871917// ============================================================================
872918
919+ /// Convert BGRA pixel data to RGBA8888
920+ ///
921+ /// ClearCodec produces BGRA output per [MS-RDPEGFX 2.2.4.1]. Reorder to
922+ /// [R, G, B, A] for the uniform `BitmapUpdate` pixel format.
923+ fn convert_bgra_to_rgba ( src : & [ u8 ] ) -> Vec < u8 > {
924+ debug_assert ! ( src. len( ) % 4 == 0 , "BGRA input length not aligned to 4 bytes" ) ;
925+ let mut dst = Vec :: with_capacity ( src. len ( ) ) ;
926+ for pixel in src. chunks_exact ( 4 ) {
927+ dst. extend_from_slice ( & [ pixel[ 2 ] , pixel[ 1 ] , pixel[ 0 ] , pixel[ 3 ] ] ) ;
928+ }
929+ dst
930+ }
931+
873932/// Convert uncompressed 32bpp little-endian pixels to RGBA8888
874933///
875934/// The wire format for uncompressed graphics is 0xAARRGGBB in a 32-bit
@@ -1031,6 +1090,24 @@ mod tests {
10311090 assert_eq ! ( cropped. len( ) , 1920 * 1080 * 4 ) ;
10321091 }
10331092
1093+ #[ test]
1094+ fn convert_bgra_to_rgba_reorders_channels ( ) {
1095+ // BGRA input: [B, G, R, A] per pixel
1096+ let bgra = vec ! [
1097+ 0xFF , 0x00 , 0x00 , 0xCC , // B=255, G=0, R=0, A=204 (blue)
1098+ 0x00 , 0xFF , 0x00 , 0x80 , // B=0, G=255, R=0, A=128 (green)
1099+ ] ;
1100+ let rgba = convert_bgra_to_rgba ( & bgra) ;
1101+ // Expected: [R, G, B, A] per pixel
1102+ assert_eq ! (
1103+ rgba,
1104+ vec![
1105+ 0x00 , 0x00 , 0xFF , 0xCC , // R=0, G=0, B=255, A=204
1106+ 0x00 , 0xFF , 0x00 , 0x80 , // R=0, G=255, B=0, A=128
1107+ ]
1108+ ) ;
1109+ }
1110+
10341111 #[ test]
10351112 fn convert_uncompressed_bgrx_to_rgba ( ) {
10361113 // Wire format: [B, G, R, A] per pixel (0xAARRGGBB little-endian)
0 commit comments