feat(session): expose FrameEnd signal for protocol-level frame synchronization#1189
feat(session): expose FrameEnd signal for protocol-level frame synchronization#1189sfwwslm (sfwwslm) wants to merge 1 commit intoDevolutions:masterfrom
Conversation
| /// See [\[MS-RDPBCGR\] 2.2.15.1]. | ||
| /// See [[MS-RDPBCGR] 2.2.15.1]. | ||
| /// | ||
| /// [\[MS-RDPBCGR\] 2.2.15.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/de783158-8b01-4818-8fb0-62523a5b3490 | ||
| /// [[MS-RDPBCGR] 2.2.15.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/de783158-8b01-4818-8fb0-62523a5b3490 | ||
| MultitransportRequest(MultitransportRequestPdu), | ||
| } | ||
|
|
||
| /// End of a logical frame. | ||
| FrameEnd, | ||
| } | ||
| impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput { |
There was a problem hiding this comment.
issue: The new code looks broken syntactically?
There was a problem hiding this comment.
Pull request overview
This PR exposes the RDPEGFX FrameEnd (frame marker end) signal up through ironrdp-session’s ActiveStageOutput, enabling protocol-aligned “present”/flush timing for downstream render loops.
Changes:
- Add a
FrameEndmarker tofast_path::UpdateKindand detectSurfaceCommand::FrameMarker(FrameAction::End)in surface command processing. - Propagate the internal
UpdateKind::FrameEndto a new publicActiveStageOutput::FrameEnd. - Minor doc-link formatting change in
ActiveStageOutputdocs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| crates/ironrdp-session/src/fast_path.rs | Detect RDPEGFX frame-end markers during surface command processing and surface them as a new update kind. |
| crates/ironrdp-session/src/active_stage.rs | Expose frame-end as a new ActiveStageOutput variant and map from fast-path updates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| processor_updates.push(UpdateKind::Region(update_region)); | ||
| processor_updates.extend(markers); |
There was a problem hiding this comment.
process_surface_commands aggregates updates across all commands, but process_single_update always emits UpdateKind::Region(...) before any FrameEnd markers. This reorders FrameEnd relative to the actual command stream and makes it impossible to associate a FrameEnd with the precise set of updates preceding it (e.g., if multiple frame markers appear in one SurfaceCommands update, or if an End marker is not the last command). Consider emitting UpdateKind values in command order (and flushing the accumulated region when encountering FrameAction::End) so FrameEnd matches the image state at that point.
| processor_updates.push(UpdateKind::Region(update_region)); | |
| processor_updates.extend(markers); | |
| processor_updates.extend(markers); | |
| processor_updates.push(UpdateKind::Region(update_region)); |
There was a problem hiding this comment.
Maybe false positive, but double check
99e62e3 to
54971c3
Compare
54971c3 to
a6b4109
Compare
Benoît Cortier (CBenoit)
left a comment
There was a problem hiding this comment.
The change is looking good to me! But I think this may be breaking other crates in the workspace (I can think of at least ironrdp-web and ironrdp-client), and this will need to be addressed before we can merge. Also, can you confirm you tested this on your tauri client? Just to be certain about the expected result.
|
Thanks for the review! I have tested this change in my Tauri-based RDP client, and everything is working as expected on my side. For context, my project does not use the However, I’m not able to fully assess the impact of this change on other crates in the workspace that I’m not using. ReferenceBranch: Dependencies: ironrdp = { git = "https://github.com/sfwwslm/IronRDP", branch = "fluxterm-runtime", features = ["session", "input", "graphics", "dvc", "svc", "displaycontrol", "connector"] }
ironrdp-tls = { git = "https://github.com/sfwwslm/IronRDP", branch = "fluxterm-runtime", package = "ironrdp-tls", features = ["rustls"] }
ironrdp-tokio = { git = "https://github.com/sfwwslm/IronRDP", branch = "fluxterm-runtime", package = "ironrdp-tokio", features = ["reqwest"] } |
|
Good to see this. We hit the same "session layer consumes protocol data without propagating it" pattern with share_id (#1147), correctly decoded but never stored for downstream use. Nice approach with the bool flag refactor. Would you find exposing other protocol signals alongside FrameEnd valuable? There are several other PDUs in a similar situation (FrameStart, codec statistics, connection health signals) that application code could use if they were surfaced. It could be great follow-up material. I'm always glad to see more IronRDP clients. I have several testing clients for my server work but I'd be interested in trying yours if it's available. I've tested frame pacing against lamco-rdp-server and can confirm the tearing artifacts you describe are visible during fast window dragging. |
|
Thank you for the detailed feedback! Regarding the points raised, I’d like to share some context from my side: On FrameEnd Signaling: During development, I noticed other signaling mechanisms within the protocol. I agree that FrameEnd (and potentially other related signals) should be exposed or handled in a way that allows downstream implementations to utilize them properly. This would ensure better alignment with the RDP specifications. Current Project Status: My client is still in an early development stage, and I am working on fully eliminating screen tearing. As I am still getting up to speed with the complexities of the RDP protocol, my immediate goal is to achieve a "functional/usable" state for my personal tool. Contribution Limits: Honestly, due to my current experience level with the protocol and limited personal time for this project, I may not be able to provide deep architectural feedback or implement complex upstream enhancements at this moment. |
|
sfwwslm (@sfwwslm) Well that all sounds great to me. I'm very pleased to hear you're focusing on an extremely important quality issue like tearing. That's jsut the sort of thing I never have time to work on. I work on architecture a great deal, and my products use a web of protocols, strategies and methods/quirks to construct an RDP session. It's all a little maddening, really, especially with all these protocols being on the bleeding edge of near-stability. If you have a particular quality issue you want to work on, post an issue here, and I'll do what i can to investigate the tooling that might be desirable to help you out. Thanks and regards, |
|
Thank you for the detailed feedback and for your time reviewing this! After a round of testing on my side, I've confirmed that adding the FrameEnd flag did not achieve the expected results for my current implementation. Therefore, I’ve decided to close this PR for now. If I have any new findings or a more robust solution in the future, I’ll be sure to open a new issue or PR to discuss it. Thanks again for the guidance! |
Summary
This PR exposes the
FrameEndmarker from the RDP Graphics Pipeline Extension ([MS-RDPEGFX]) to theActiveStageOutput. This allows downstream consumers to synchronize their rendering loop with the server's logical frames, effectively eliminating screen tearing and improving frame pacing.Background
In the [MS-RDPEGFX] protocol, the server sends
RDPGFX_FRAME_MARKER_PDUwith aframeActionofRDPGFX_FRAME_END(0x0001) to signal the completion of a logical frame. Whileironrdp-pdualready supports parsing these markers, theActiveStageinironrdp-sessionwas previously consuming them internally (for Acknowledge packets) without notifying the application layer.Without this signal, RDP clients often have to rely on arbitrary timers (e.g., 16ms) to flush pending graphics updates. This frequently leads to "half-rendered" updates appearing on screen when a server-side frame is split across multiple PDUs, causing visible tearing and "rolling" artifacts during fast window dragging or scrolling.
Changes
FrameEndvariant to the internalUpdateKindenum.Processor::process_surface_commandsto detectFrameMarkerwithFrameAction::Endand propagate it as anUpdateKind.FrameEndvariant to the publicActiveStageOutputenum.ActiveStage::processto transform the internalUpdateKind::FrameEndinto an externalActiveStageOutput::FrameEnd.Impact
By consuming the
FrameEndsignal, applications can now:requestAnimationFramein web clients) with the server's actual production rate.Empirical testing in a Tauri-based WebView RDP client showed that using this marker allowed for a stable 60+ FPS experience with significantly improved visual coherence during intense UI movements.