Skip to content

Add some developer debug around missed present deadlines - #245

Merged
TheElixZammuto merged 3 commits into
TheElixZammuto:masterfrom
andygrundman:andyg.xbox-one-render-loop
Jan 11, 2026
Merged

TheElixZammuto merged 3 commits into
TheElixZammuto:masterfrom
andygrundman:andyg.xbox-one-render-loop

Conversation

@TheElixZammuto

@TheElixZammuto TheElixZammuto commented Jan 10, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Removed Xbox One tearing workaround for more consistent frame timing.
  • Improvements

    • Added deadline hit/miss tracking surfaced in verbose debug output for diagnostics.
    • Render pacing now reports whether presentation deadlines were met, improving timing visibility.
    • More accurate timing metrics for decode and render stages.
    • Warning console now uses the same font as the main console.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 10, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

State and streaming code now track whether frame-present deadlines are met. Pacer.waitBeforePresent returns a boolean indicating whether a wait occurred, callers propagate that as hitDeadline into SubmitRenderStats, VIDEO_STATS gains hitDeadlines/missedDeadlines counters which are aggregated and surfaced in debug output.

Changes

Cohort / File(s) Change summary
Stats & public types
State/Stats.h, State/Stats.cpp
Added VIDEO_STATS.hitDeadlines and VIDEO_STATS.missedDeadlines; Stats::SubmitRenderStats signature gains bool hitDeadline; counters incremented and aggregated; debug formatting updated to emit missed-present rates and per-frame preWait/render times.
Frame pacing control
Streaming/Pacer.h, Streaming/Pacer.cpp
waitBeforePresent(int64_t) return type changed from void to bool; implementation now returns whether the call slept/waited (i.e., hit deadline). Logging added in init.
Call sites propagating deadline info
Streaming/moonlight_xbox_dxMain.cpp
Caller captures Pacer::waitBeforePresent boolean return as hitDeadline and passes it to SubmitRenderStats.
Decoder timing/reporting
Streaming/FFmpegDecoder.h, Streaming/FFmpegDecoder.cpp
Added fps field; removed Xbox One sleep tearing workaround; decode duration computed once and forwarded to stats via SubmitDecodeMs.
Device / renderer logging
Common/DeviceResources.cpp, Streaming/LogRenderer.cpp
Added debug logging for swapchain panel size/scales; warning console font now matches main console font selection.

Sequence Diagram(s)

sequenceDiagram
  participant Pacer
  participant MainLoop as App/Main
  participant Stats
  participant DeviceRes as DeviceResources

  App/Main->>Pacer: waitBeforePresent(deadline)
  Pacer-->>App/Main: bool waited (hitDeadline)
  App/Main->>DeviceRes: Present()
  App/Main->>Stats: SubmitRenderStats(preWaitUs, renderUs, presentUs, hitDeadline)
  Stats->>Stats: increment hitDeadlines/missedDeadlines and aggregate VIDEO_STATS
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

"I hopped through frames both wide and tight,
Counters ticked by soft moonlight.
If deadline kissed the present true,
I tally hops — a win or two.
Render on, dear friends — the rabbit cheers!" 🐰

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the changeset: adding deadline tracking and debug logging for missed present deadlines.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4719e and 296ba54.

📒 Files selected for processing (3)
  • Common/DeviceResources.cpp
  • Streaming/Pacer.cpp
  • Streaming/moonlight_xbox_dxMain.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • Common/DeviceResources.cpp
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.
📚 Learning: 2025-12-14T22:36:59.945Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:36:59.945Z
Learning: In Streaming/moonlight_xbox_dxMain.cpp, initialize lastFramePts to 0 because the first decoded frame's PTS is never 0, enabling safe detection of frame changes in the render loop. Include a clarifying comment about this assumption. If this assumption could change in the future, add a one-time initialization using the first non-zero PTS to guard against regressions.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
📚 Learning: 2026-01-10T18:24:38.508Z
Learnt from: mpotrykus
Repo: TheElixZammuto/moonlight-xbox PR: 246
File: Pages/StreamPage.xaml.cpp:77-91
Timestamp: 2026-01-10T18:24:38.508Z
Learning: In the moonlight-xbox project's StreamPage::Page_Loaded, the inner DISPATCH_UI macro (lines 83-91 in StreamPage.xaml.cpp) is necessary to ensure the loading screen displays correctly. Even though the outer RunAsync executes on the UI thread, wrapping the heavy initialization (CreateDeviceDependentResources, CreateWindowSizeDependentResources, StartRenderLoop) in a separate DISPATCH_UI breaks the work into two message queue items, allowing the UI to update and show the loading screen between the moonlight_xbox_dxMain construction and the resource initialization.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
📚 Learning: 2025-12-14T22:37:06.337Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.

Applied to files:

  • Streaming/Pacer.cpp
🧬 Code graph analysis (1)
Streaming/moonlight_xbox_dxMain.cpp (1)
Streaming/Pacer.cpp (2)
  • instance (40-43)
  • instance (40-40)
🔇 Additional comments (4)
Streaming/moonlight_xbox_dxMain.cpp (2)

141-142: LGTM!

The return value from waitBeforePresent is correctly captured and the variable is well-scoped within the critical section where it's used.


173-177: LGTM!

The hitDeadline boolean is correctly propagated to SubmitRenderStats for deadline tracking statistics.

Streaming/Pacer.cpp (2)

89-91: LGTM!

Useful debug logging for diagnosing frame pacing configuration. The ternary clearly indicates the mode being used.


332-349: LGTM!

The boolean return semantics are clear and well-documented in the comment:

  • Returns true when the function waited (deadline hit)
  • Returns false when the target was already passed (deadline missed)

This aligns with how the caller uses the value for deadline hit/miss tracking.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @State/Stats.cpp:
- Line 399: The missed-present-rate calculation uses `stats.hitDeadlines ? ... :
0.0f` which returns 0 when there are only missed deadlines; change the
conditional to check the total (`stats.missedDeadlines + stats.hitDeadlines`)
and compute `((double)stats.missedDeadlines / (stats.missedDeadlines +
stats.hitDeadlines)) * 100` when the total is non-zero, otherwise return 0.0f,
ensuring the numerator/denominator use a floating-point cast to avoid integer
division.

In @Streaming/moonlight_xbox_dxMain.cpp:
- Around line 111-115: Remove the unused local variables hits and misses
declared alongside ewmaRenderMs; they are vestigial and not referenced anywhere
(deadline tracking is done via Stats::SubmitRenderStats). Edit
moonlight_xbox_dxMain.cpp to delete the declarations "int hits = 0, misses = 0"
(or remove hits and misses from that variable declaration) so only ewmaRenderMs
remains, and run a quick build to ensure no remaining references to hits/misses
exist.
🧹 Nitpick comments (1)
Streaming/FFmpegDecoder.cpp (1)

294-303: Consider removing the commented-out workaround code.

The commented-out Xbox One tearing workaround has been replaced by deadline-based timing. While the comments provide historical context, keeping dead code can clutter the codebase. Consider either:

  1. Removing the block entirely and documenting the rationale in the commit message or a code comment
  2. If this is intentionally kept for potential rollback during testing, add a TODO comment with a tracking issue
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3993f9a and 1b5e3d8.

📒 Files selected for processing (7)
  • State/Stats.cpp
  • State/Stats.h
  • Streaming/FFmpegDecoder.cpp
  • Streaming/FFmpegDecoder.h
  • Streaming/Pacer.cpp
  • Streaming/Pacer.h
  • Streaming/moonlight_xbox_dxMain.cpp
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.
📚 Learning: 2025-12-14T22:37:06.337Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.

Applied to files:

  • Streaming/Pacer.h
  • Streaming/FFmpegDecoder.cpp
📚 Learning: 2025-12-14T22:36:59.945Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:36:59.945Z
Learning: In Streaming/moonlight_xbox_dxMain.cpp, initialize lastFramePts to 0 because the first decoded frame's PTS is never 0, enabling safe detection of frame changes in the render loop. Include a clarifying comment about this assumption. If this assumption could change in the future, add a one-time initialization using the first non-zero PTS to guard against regressions.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
📚 Learning: 2026-01-08T15:15:34.393Z
Learnt from: mpotrykus
Repo: TheElixZammuto/moonlight-xbox PR: 243
File: moonlight-xbox-dx.vcxproj:661-663
Timestamp: 2026-01-08T15:15:34.393Z
Learning: In the moonlight-xbox project (moonlight-xbox-dx.vcxproj), the ProjectReference to third_party\DirectXTK\DirectXTK_Windows10_2022.vcxproj is intentionally added before the file exists. The setup-dev.ps1 script generates this project file during setup, and the reference ensures the correct build order (DirectXTK before main project) once everything is generated, avoiding manual build requirements.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
🧬 Code graph analysis (3)
State/Stats.h (1)
State/Stats.cpp (2)
  • SubmitRenderStats (136-156)
  • SubmitRenderStats (136-136)
Streaming/Pacer.h (1)
Streaming/Pacer.cpp (2)
  • waitBeforePresent (330-347)
  • waitBeforePresent (330-330)
Streaming/moonlight_xbox_dxMain.cpp (2)
Streaming/Pacer.cpp (2)
  • instance (40-43)
  • instance (40-40)
pch.h (1)
  • QpcToUs (68-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
Streaming/FFmpegDecoder.h (1)

39-39: LGTM!

The addition of the fps field follows the existing pattern for other stream configuration values (videoFormat, width, height). This enables deadline-based timing calculations elsewhere in the codebase.

Streaming/Pacer.h (1)

25-25: LGTM!

The signature change from void to bool enables callers to determine whether the wait succeeded, which is essential for the deadline tracking feature being introduced.

Streaming/Pacer.cpp (1)

329-347: LGTM!

The implementation correctly returns true when a wait occurred (target was in the future) and false when no wait happened (either not running or already past the deadline). The comment accurately describes the semantics.

State/Stats.h (2)

29-30: LGTM!

The new counter fields are appropriately typed as uint32_t and placed logically near other frame-related statistics.


66-66: LGTM!

The signature update to include the hitDeadline parameter aligns with the implementation and enables deadline tracking throughout the stats pipeline.

Streaming/FFmpegDecoder.cpp (2)

89-91: LGTM!

The initialization of fps from the configuration is correctly placed alongside the Pacer initialization which uses config->fps.


98-98: LGTM!

Setting a default of 60 fps is a reasonable fallback until CompleteInitialization sets the actual value from config.

Streaming/moonlight_xbox_dxMain.cpp (2)

142-142: LGTM!

Correctly captures the boolean return from waitBeforePresent to track whether the deadline was met.


174-178: LGTM!

The hitDeadline flag is properly passed to SubmitRenderStats, enabling centralized deadline tracking in the Stats class.

State/Stats.cpp (2)

136-156: LGTM!

The deadline tracking logic correctly increments the appropriate counter and includes debug logging for missed deadlines to aid in diagnosing timing issues.


167-168: LGTM!

Correctly propagates the deadline counters during statistics window aggregation.

Comment thread State/Stats.cpp
"------\n"
"Missed present rate: %.2f%%\n"
"PreWait/Render: %.2f/%.2f ms\n",
stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Logic issue in missed present rate calculation.

The condition stats.hitDeadlines ? ... : 0.0f will incorrectly show 0% if there are only missed deadlines (hitDeadlines=0, missedDeadlines>0). The check should verify the total is non-zero instead.

🐛 Proposed fix
-					   stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
+					   (stats.hitDeadlines + stats.missedDeadlines) ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
(stats.hitDeadlines + stats.missedDeadlines) ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
🤖 Prompt for AI Agents
In @State/Stats.cpp at line 399, The missed-present-rate calculation uses
`stats.hitDeadlines ? ... : 0.0f` which returns 0 when there are only missed
deadlines; change the conditional to check the total (`stats.missedDeadlines +
stats.hitDeadlines`) and compute `((double)stats.missedDeadlines /
(stats.missedDeadlines + stats.hitDeadlines)) * 100` when the total is non-zero,
otherwise return 0.0f, ensuring the numerator/denominator use a floating-point
cast to avoid integer division.

Comment thread Streaming/moonlight_xbox_dxMain.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @Streaming/moonlight_xbox_dxMain.cpp:
- Line 115: Remove the unused local variables `hits` and `misses` declared in
the render loop (the `int hits = 0, misses = 0;` line) — they are redundant
because deadline tracking is already done by the `Stats` class (`hitDeadlines` /
`missedDeadlines`) when `SubmitRenderStats` is called; simply delete that
declaration from moonlight_xbox_dxMain.cpp and ensure there are no remaining
references to `hits` or `misses` elsewhere in the render loop.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc664a0 and 4c4719e.

📒 Files selected for processing (3)
  • Common/DeviceResources.cpp
  • Streaming/Pacer.cpp
  • Streaming/moonlight_xbox_dxMain.cpp
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.
📚 Learning: 2025-12-14T22:36:59.945Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:36:59.945Z
Learning: In Streaming/moonlight_xbox_dxMain.cpp, initialize lastFramePts to 0 because the first decoded frame's PTS is never 0, enabling safe detection of frame changes in the render loop. Include a clarifying comment about this assumption. If this assumption could change in the future, add a one-time initialization using the first non-zero PTS to guard against regressions.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
📚 Learning: 2026-01-10T18:24:38.508Z
Learnt from: mpotrykus
Repo: TheElixZammuto/moonlight-xbox PR: 246
File: Pages/StreamPage.xaml.cpp:77-91
Timestamp: 2026-01-10T18:24:38.508Z
Learning: In the moonlight-xbox project's StreamPage::Page_Loaded, the inner DISPATCH_UI macro (lines 83-91 in StreamPage.xaml.cpp) is necessary to ensure the loading screen displays correctly. Even though the outer RunAsync executes on the UI thread, wrapping the heavy initialization (CreateDeviceDependentResources, CreateWindowSizeDependentResources, StartRenderLoop) in a separate DISPATCH_UI breaks the work into two message queue items, allowing the UI to update and show the loading screen between the moonlight_xbox_dxMain construction and the resource initialization.

Applied to files:

  • Streaming/moonlight_xbox_dxMain.cpp
📚 Learning: 2025-12-14T22:37:06.337Z
Learnt from: andygrundman
Repo: TheElixZammuto/moonlight-xbox PR: 239
File: Streaming/moonlight_xbox_dxMain.cpp:150-159
Timestamp: 2025-12-14T22:37:06.337Z
Learning: In the moonlight-xbox streaming application, the first decoded frame's PTS (presentation timestamp) will never be 0, so initializing lastFramePts to 0 is safe for detecting frame changes in the render loop.

Applied to files:

  • Streaming/Pacer.cpp
🧬 Code graph analysis (1)
Common/DeviceResources.cpp (2)
Utils.cpp (2)
  • Logf (90-99)
  • Logf (90-90)
Utils.hpp (1)
  • Logf (15-15)
🔇 Additional comments (4)
Common/DeviceResources.cpp (1)

377-379: LGTM! Helpful debug logging for swap chain configuration.

The logging statement is well-placed and captures useful information about the SwapChainPanel's logical size and composition scales right before resource creation. This aligns with the PR's debugging objectives and follows the existing logging pattern in this file (similar to lines 223-224 and 289-290). The format specifiers correctly match the data types, and the performance impact is negligible since this isn't a hot path.

Streaming/moonlight_xbox_dxMain.cpp (1)

142-142: LGTM! Deadline tracking integration looks correct.

The implementation properly captures the deadline hit status from waitBeforePresent and propagates it to SubmitRenderStats, enabling the tracking of present deadline performance as described in the PR objectives.

Also applies to: 174-178

Streaming/Pacer.cpp (2)

89-91: LGTM! Useful debug logging added.

The initialization logging provides valuable context about frame pacing configuration, which aligns well with the PR's goal of adding developer debug information around frame timing.


332-350: LGTM! Return value semantics are correct.

The boolean return logic correctly indicates whether the deadline was met:

  • Returns true when there's time to wait before the deadline (hit)
  • Returns false when the deadline has already passed (missed)

This aligns properly with the caller's expectations in moonlight_xbox_dxMain.cpp where the result is captured as hitDeadline.

Comment thread Streaming/moonlight_xbox_dxMain.cpp Outdated
@andygrundman
andygrundman force-pushed the andyg.xbox-one-render-loop branch from 4c4719e to 296ba54 Compare January 10, 2026 19:31
@TheElixZammuto
TheElixZammuto merged commit e895032 into TheElixZammuto:master Jan 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants