Skip to content

[tracking] Client-side Graphics Pipeline (EGFX): wire the existing decode core into the connector and client stack #1464

Description

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 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

  1. 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.)
  2. 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.
  3. 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.
  4. Codec coverage for real servers. grd sends non-AVC codecs, so making its output renderable needs ClearCodec (feat(egfx): add ClearCodec client-side decode dispatch #1175) and Progressive (feat(egfx): wire RemoteFX Progressive decode into WireToSurface2 dispatch #1443). The surface-composition PDUs (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.
  5. 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.)

What is already in the pipeline

PR/issue Author What it is State
#1460 Lamco client-side EGFX surface compositor (items 3, 4-composition/cache) OPEN, filed 2026-07-20
#1461 Lamco session-side render: drain compositor into DecodedImage (item 3) OPEN, filed 2026-07-20, stacked on #1377+#1460
#1462 Lamco native-client registration of the graphics DVC (item 2) OPEN, filed 2026-07-20, stacked on #1377/#1460/#1461
#1237 GlassOnTin connector SUPPORT_DYN_VC_GFX_PROTOCOL early-cap flag (item 1) OPEN, rebased (per GlassOnTin 2026-07-20)
#1175 Lamco ClearCodec client decode dispatch (item 4) OPEN, mergeable
#1443 truebest Progressive decode into WireToSurface2 dispatch (item 4) OPEN, mergeable
#1455 digyear preserve AVC_DISABLED during negotiation (item 5) OPEN
#1197, #1057, #1099 Lamco ProgressiveDecoder, egfx crate + server (merged) done
#1158 Lamco server-side multi-codec EGFX (context) open

Suggested order

  1. Connector flag first (feat(connector)!: expose SUPPORT_DYN_VC_GFX_PROTOCOL early-cap flag for EGFX clients #1237). Nothing can be tested end to end until the server opens the graphics channel. This is the gate (GlassOnTin's PR, rebased).
  2. Rendering foundation (filed). The client-side GraphicsPipelineClient becomes 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).
  3. Codec coverage (feat(egfx): add ClearCodec client-side decode dispatch #1175 ClearCodec, feat(egfx): wire RemoteFX Progressive decode into WireToSurface2 dispatch #1443 Progressive) so a real grd session renders more than composition and uncompressed content, plus H.264 decoder wiring in the native client so AVC renders.
  4. CapsAdvertise correctness so the advertised set matches the decoders present (coordinate with fix(egfx): preserve AVC_DISABLED during negotiation #1455).
  5. 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):

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.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions