You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IronRDP has a working client-side EGFX (MS-RDPEGFX Graphics Pipeline) decode core in ironrdp-egfx, but nothing above it was connected: the connector never advertised graphics-pipeline support, and neither ironrdp-client nor ironrdp-web opened the graphics channel or rendered what came back, so IronRDP clients couldn't use the Graphics Pipeline at all. I'm opening this to track wiring it end to end. The three foundational pieces are already filed as PRs (see the pipeline table): the client-side surface compositor (#1460), the session-side render that drains it (#1461), and native-client registration of the graphics channel (#1462). This issue tracks those, the remaining codec-coverage and negotiation-correctness work, and the coordination with the DVC and client refactors in flight. The sequencing below is a suggested cut, not a fixed plan.
Why this is now high priority
Recent GNOME Remote Desktop (reproduced on 49 and reported on 50) removed its legacy RDP graphics paths and now requires the Graphics Pipeline. A client that doesn't advertise EGFX is rejected during capability exchange:
[RDP] Client did not advertise support for the Graphics Pipeline, closing connection
[rdp_peer_handle_state_demand_active]: [CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE] Capabilities() callback failed
So this isn't an optional codec feature anymore; it's the requirement for connecting to a large and growing class of servers (every modern GNOME desktop, and the direction of travel is EGFX-mandatory more broadly). Remmina hit the identical wall (Remmina#3302), and #1446 is the concrete report. Both ironrdp-web and the native ironrdp-client are affected.
A personal note
Since my products are server-focused, I haven't worried too much about the client side, but the need's becoming acute.
What already exists (the hard part)
ironrdp-egfx/src/client.rs (GraphicsPipelineClient) is a complete state machine: capability negotiation (parses V8 through V10.7), full surface lifecycle (Create/Delete/Map/Reset), the frame loop with FrameAcknowledge, and it processes every GfxPdu. It natively decodes AVC420 and Uncompressed, and codec dispatch for ClearCodec (#1175) and RemoteFX Progressive (#1443) is in review. The decode engine turns over; it's just not connected to anything.
What is missing
Connector: advertise the pipeline. The client core data never sets RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL in earlyCapabilityFlags. Without it the server never opens the Microsoft::Windows::RDS::Graphics channel, so the decode core is never reached. This is feat(connector)!: expose SUPPORT_DYN_VC_GFX_PROTOCOL early-cap flag for EGFX clients #1237. (Related invariant: grd also rejects a client that advertises graphics but not 32-bit colour depth, so WANT_32_BPP_SESSION must be set at the same time.)
Client/web: open the channel. Neither ironrdp-client nor ironrdp-web registers GraphicsPipelineClient as a DVC. ironrdp-egfx is currently a dependency of ironrdp-server only.
Client/web: render the output. There's no path from the handler's BitmapUpdate callback into the client's framebuffer/canvas. ironrdp-web currently renders only legacy surface commands.
Advertise only what we can decode. The server picks the highest capability set the client advertises (MS-RDPEGFX 3.2.5.19). Advertising a codec the client can't decode (e.g. AVC444 without an AVC444 decoder) makes the server send it and produces a blank screen. The CapsAdvertise must match real decode capability. (This is the same negotiation-correctness area as fix(egfx): preserve AVC_DISABLED during negotiation #1455.)
Robustness: flow-control queueDepth, optional QoE frame ack, HiDPI scaled-output maps (grd 50 shipped HiDPI), surface-memory policy, and EGFX/DisplayControl coordination on resize (MS-RDPEDISP 1.3 restarts the pipeline via EGFX surface commands when EGFX is in use, not deactivate-reactivate).
Coordination with refactoring underway
This work touches the connector, the DVC layer, the session, and the client crates, all of which have active or recently landed refactors. Verified state (2026-07-20):
The web client architecture is itself an open question. The current ironrdp-web may be superseded by a WASM-first client (see ironrdp-web becomes unresponsive when remote framebuffer updated rapidly #790), so the browser rendering integration should land against whichever web architecture settles rather than the current ironrdp-web internals. The native client and the protocol/decode layers are architecture-neutral and can proceed regardless.
grd requires this and reproduces it reliably. We have a grd 49 rig locally and strausmann has offered a grd 50 instance (#1446) to validate a wiring branch end to end.
Summary
IronRDP has a working client-side EGFX (MS-RDPEGFX Graphics Pipeline) decode core in
ironrdp-egfx, but nothing above it was connected: the connector never advertised graphics-pipeline support, and neitherironrdp-clientnorironrdp-webopened the graphics channel or rendered what came back, so IronRDP clients couldn't use the Graphics Pipeline at all. I'm opening this to track wiring it end to end. The three foundational pieces are already filed as PRs (see the pipeline table): the client-side surface compositor (#1460), the session-side render that drains it (#1461), and native-client registration of the graphics channel (#1462). This issue tracks those, the remaining codec-coverage and negotiation-correctness work, and the coordination with the DVC and client refactors in flight. The sequencing below is a suggested cut, not a fixed plan.Why this is now high priority
Recent GNOME Remote Desktop (reproduced on 49 and reported on 50) removed its legacy RDP graphics paths and now requires the Graphics Pipeline. A client that doesn't advertise EGFX is rejected during capability exchange:
So this isn't an optional codec feature anymore; it's the requirement for connecting to a large and growing class of servers (every modern GNOME desktop, and the direction of travel is EGFX-mandatory more broadly). Remmina hit the identical wall (Remmina#3302), and #1446 is the concrete report. Both
ironrdp-weband the nativeironrdp-clientare affected.A personal note
Since my products are server-focused, I haven't worried too much about the client side, but the need's becoming acute.
What already exists (the hard part)
ironrdp-egfx/src/client.rs(GraphicsPipelineClient) is a complete state machine: capability negotiation (parses V8 through V10.7), full surface lifecycle (Create/Delete/Map/Reset), the frame loop withFrameAcknowledge, and it processes everyGfxPdu. It natively decodes AVC420 and Uncompressed, and codec dispatch for ClearCodec (#1175) and RemoteFX Progressive (#1443) is in review. The decode engine turns over; it's just not connected to anything.What is missing
RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOLinearlyCapabilityFlags. Without it the server never opens theMicrosoft::Windows::RDS::Graphicschannel, so the decode core is never reached. This is feat(connector)!: expose SUPPORT_DYN_VC_GFX_PROTOCOL early-cap flag for EGFX clients #1237. (Related invariant: grd also rejects a client that advertises graphics but not 32-bit colour depth, soWANT_32_BPP_SESSIONmust be set at the same time.)ironrdp-clientnorironrdp-webregistersGraphicsPipelineClientas a DVC.ironrdp-egfxis currently a dependency ofironrdp-serveronly.BitmapUpdatecallback into the client's framebuffer/canvas.ironrdp-webcurrently renders only legacy surface commands.SolidFill,SurfaceToSurface) and the surface cache (SurfaceToCache/CacheToSurface/EvictCacheEntry) are now applied by the compositor in feat(egfx): composite client surface commands into pixel buffers #1460 rather than forwarded.CapsAdvertisemust match real decode capability. (This is the same negotiation-correctness area as fix(egfx): preserve AVC_DISABLED during negotiation #1455.)What is already in the pipeline
DecodedImage(item 3)SUPPORT_DYN_VC_GFX_PROTOCOLearly-cap flag (item 1)WireToSurface2dispatch (item 4)AVC_DISABLEDduring negotiation (item 5)Suggested order
GraphicsPipelineClientbecomes a surface compositor (feat(egfx): composite client surface commands into pixel buffers #1460); the session drains it into the decoded image and emits graphics updates (feat(session): render the EGFX graphics pipeline output #1461); the native client registers the graphics DVC (feat(client): register the graphics pipeline dynamic virtual channel #1462). These are stacked on the DVC access-API refactor in flight (fix(dvc)!: replace DVC wrappers with typed accessors #1377: typed accessors), plus a by-type mutable accessor added in feat(session): render the EGFX graphics pipeline output #1461. Native client first; the browser rendering path follows the web-architecture decision (see coordination note below).queueDepth, optional QoE frame ack, HiDPI scaled-output maps (grd 50 shipped HiDPI), surface-memory policy, and EGFX/DisplayControl coordination on resize (MS-RDPEDISP 1.3 restarts the pipeline via EGFX surface commands when EGFX is in use, not deactivate-reactivate).Coordination with refactoring underway
This work touches the connector, the DVC layer, the session, and the client crates, all of which have active or recently landed refactors. Verified state (2026-07-20):
refactor(dvc)!, open/mergeable). feat(dvc): expose dynamic channel accessors #1368 (merged 06-23) exposed dynamic-channel accessors; fix(dvc)!: replace DVC wrappers with typed accessors #1377 replaces the wrapper types with typed accessors and makesDynamicVirtualChannelprivate. Post-fix(dvc)!: replace DVC wrappers with typed accessors #1377 the access shape isActiveStage::get_dvc::<T>() -> Option<DynamicChannelRef<'_, T>>(plusget_dvc_by_channel_id/..._mut), the processor trait isDvcClientProcessor, and the oldchannel_processor_downcast_refis gone. Registration is unchanged (DrdynvcClient::with_dynamic_channel(...)).ironrdp-egfx'sGraphicsPipelineClientalready implementsDvcProcessor+DvcClientProcessor, and fix(dvc)!: replace DVC wrappers with typed accessors #1377 does not touchironrdp-egfx, so no companion egfx change is required; the wiring's only fix(dvc)!: replace DVC wrappers with typed accessors #1377-sensitivity is that its access call usesget_dvc::<GraphicsPipelineClient>()instead of a downcast. The wiring PR should therefore be built on fix(dvc)!: replace DVC wrappers with typed accessors #1377 (or land after it); coordinate merge order with uchouT.ironrdp-connector(enforced). fix(session)!: reduce dependency on ironrdp-connector #1419 + fix(session)!: remove ironrdp-connector dependency #1435 (merged) removed that dependency and ci(xtask): add dependency-invariant guard for ironrdp-session #1438 added a CI guard for it. The EGFX wiring is connector-independent (it goes throughDrdynvcClient+ the handler), which keeps it on the right side of the guard; any session-side glue must not reach for connector types.ironrdp-clientsplit, touchesrdp.rs) is open but stale (review-required, untouched since 2026-05-28), so lower near-term collision risk; watch for revival. Switchironrdp-client/ironrdp-web/ffi to the new CredSSP API; migrateironrdp-weberror mapping #1431 (an issue, not a PR) is the future client/web/ffi CredSSP migration on the same files.ironrdp-webmay be superseded by a WASM-first client (see ironrdp-web becomes unresponsive when remote framebuffer updated rapidly #790), so the browser rendering integration should land against whichever web architecture settles rather than the currentironrdp-webinternals. The native client and the protocol/decode layers are architecture-neutral and can proceed regardless.Validation
grd requires this and reproduces it reliably. We have a grd 49 rig locally and strausmann has offered a grd 50 instance (#1446) to validate a wiring branch end to end.