Skip to content

120fps frame pacing, async video decoder - #215

Merged
TheElixZammuto merged 22 commits into
TheElixZammuto:masterfrom
andygrundman:andyg.xbox-async-decoder
Nov 1, 2025
Merged

TheElixZammuto merged 22 commits into
TheElixZammuto:masterfrom
andygrundman:andyg.xbox-async-decoder

Conversation

@andygrundman

@andygrundman andygrundman commented Oct 29, 2025 •

Copy link
Copy Markdown
Collaborator

I think this is ready for a proper release. It's received pretty positive reviews from a handful of testers.

Frame Pacing:

  • Refactored and optimized FFMpegDecoder to run on its own thread.
  • Ultra-smooth frame pacing originally based on moonlight-qt.
  • Automatic support for NTSC refresh rates 59.94 and 119.88 when using the latest Sunshine (v2025.1025 or later).
  • Optimize D3D11 renderer by using SetMaximumFrameLatency(1), GetFrameLatencyWaitableObject, and precise Present() timing.

120fps:

  • Disables hardware vsync and uses a custom 120hz vsync emulation.
  • Some minor screen tearing is unavoidable due to Xbox limitations.
  • Xbox One X has a lower resolution clock and may not handle 120fps as smooth as Series S/X.

Misc:

  • 30/60fps streams always run with hardware vsync and won't have screen tearing.
  • Add auto-bitrate calc when changing resolution or fps.
  • Add warning to LogRenderer about it reducing performance when open.
  • Add message about HDR requiring 4K system resolution.
  • Removed host settings for tearing and vsync.
  • Update ffmpeg to 7.1.1.
  • Fix 10-bit offsets in BT2020 shader.

Summary by CodeRabbit

Release Notes

  • New Features

    • Automatic V-sync management optimized per frame rate for smoother playback
    • Enhanced frame pacing system for improved timing precision and reduced tearing
    • Xbox platform-specific optimizations for display refresh rate handling
  • UI Changes

    • Removed frame queue adjustment controls from streaming menu
    • Simplified host settings layout with streamlined performance options
    • Added warning indicator during log viewing
  • Improvements

    • Better display refresh rate detection and adaptation
    • Optimized render target handling

…nce. Try to make the locks a bit more granular.
…dProtected mode. Also removed the force tearing setting (it's the same as disabling vsync), and changed the modify frame queue to tweak the Pacer target.
…resh rate. Rework pacing code around this system.
…erly. Use only hardware vsync when streaming at <= 60fps.
… 30/60. Remove commented out D3D locking code.
…not at 4K, clean out vsync setting, fixup clientRefreshRateX100 for non-NTSC.
@coderabbitai

coderabbitai Bot commented Oct 29, 2025 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@andygrundman has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 48d6137 and 1d8d253.

📒 Files selected for processing (24)
  • Common/DeviceResources.cpp (14 hunks)
  • Common/DirectXHelper.h (1 hunks)
  • Common/TextConsole.cpp (2 hunks)
  • Pages/HostSettingsPage.xaml (2 hunks)
  • Pages/HostSettingsPage.xaml.cpp (4 hunks)
  • Plot/PlotDesc.h (2 hunks)
  • State/MoonlightClient.cpp (3 hunks)
  • State/Stats.cpp (12 hunks)
  • Streaming/FFmpegDecoder.cpp (3 hunks)
  • Streaming/FFmpegDecoder.h (1 hunks)
  • Streaming/LogRenderer.cpp (3 hunks)
  • Streaming/Pacer.cpp (1 hunks)
  • Streaming/Pacer.h (1 hunks)
  • Streaming/PacerRational.h (1 hunks)
  • Streaming/StatsRenderer.cpp (7 hunks)
  • Streaming/VideoRenderer.cpp (8 hunks)
  • Streaming/VideoRenderer.h (3 hunks)
  • Streaming/moonlight_xbox_dxMain.cpp (6 hunks)
  • Streaming/moonlight_xbox_dxMain.h (1 hunks)
  • Utils/FloatBuffer.cpp (1 hunks)
  • moonlight-xbox-dx.vcxproj (3 hunks)
  • moonlight-xbox-dx.vcxproj.filters (3 hunks)
  • pch.h (3 hunks)
  • vcpkg.json (1 hunks)

Walkthrough

This PR introduces a comprehensive frame pacing architecture alongside significant refactoring of GPU resource management, FFmpeg decoder initialization, and DirectX device configuration. Key changes include a new multi-threaded Pacer system for frame synchronization, GPU performance timing utilities, singleton-based FFmpeg decoder, and removal of legacy force-tearing controls in favor of dynamic VSYNC management. The implementation also updates device feature levels, adjusts UI settings for resolution/bitrate auto-calculation, and refines stats/plotting metrics.

Changes

Cohort / File(s) Summary
Frame Pacing System
Streaming/Pacer.h, Streaming/Pacer.cpp, Streaming/PacerRational.h
New multi-threaded frame pacing infrastructure with VSYNC emulation/hardware paths, frame submission queues, render-thread coordination, and pacing metrics collection. Includes QpcRationalPeriod for fractional refresh rates (59.94/119.88 Hz).
GPU Performance Timing
Common/DirectXHelper.h, Common/DirectXHelper.cpp
New GpuPerformanceTimer class using D3D11 queries to measure frame timing with history tracking, min/max statistics, and periodic resets.
Device Resources & VSYNC
Common/DeviceResources.h, Common/DeviceResources.cpp
Upgraded feature level to 11_1, disabled VSYNC by default, introduced frame latency waitable object, added SetVsync(bool) API, removed SetForceTearing(bool), added isXbox() helper, streamlined depth/stencil resource handling.
FFmpeg Decoder Refactoring
Streaming/FFmpegDecoder.h, Streaming/FFmpegDecoder.cpp
Converted to singleton pattern with new CompleteInitialization() and SubmitDecodeUnit() APIs; replaced legacy frame-fetch model with FFmpeg query loop and Pacer frame submission; added frame metadata attachment (MLFrameData struct); integrated multithreading safeguards.
Video Rendering
Streaming/VideoRenderer.h, Streaming/VideoRenderer.cpp
Updated Render(AVFrame* frame) signature to accept frame directly; added YUV texture management (EnsureYuvTargets); replaced frame-fetch with direct AVFrame processing; changed m_loadingComplete to atomic; updated color-space handling with 10-bit offset variants.
Main App & Render Loop
Streaming/moonlight_xbox_dxMain.h, Streaming/moonlight_xbox_dxMain.cpp
Changed m_sceneRenderer from unique_ptr to shared_ptr; integrated Pacer-based render synchronization; added input thread with 2 kHz pacing; refactored render loop to use waitForFrame() and renderOnMainThread().
Stats & Metrics
State/Stats.h, State/Stats.cpp
Replaced SubmitRenderTime() with SubmitPacerTime() and SubmitPresentPacing(); added totalPacerTimeUs and totalPresentDisplayMs fields; switched to RTP timestamp-based host frametime; updated FFMpegDecoder singleton calls.
Plotting & Visualization
Plot/PlotDesc.h, Plot/ImGuiPlots.cpp, Streaming/StatsRenderer.cpp, Streaming/LogRenderer.h, Streaming/LogRenderer.cpp
Replaced PLOT_QUEUED_FRAMES with PLOT_VSYNC_INTERVAL, added PLOT_PRESENT_PACING and PLOT_ETC; extended PlotDesc with scaleMin/scaleMax/scaleTarget/clampMax fields; refactored graph rendering with expanded buffers and dynamic window sizing; added warning console (LogRenderer).
UI Configuration
Pages/HostSettingsPage.xaml, Pages/HostSettingsPage.xaml.h, Pages/HostSettingsPage.xaml.cpp
Removed V-Sync/tearing toggle from UI; added FPSSelector with auto-bitrate adjustment; implemented getDefaultBitrate() helper using resolution/FPS tables with non-linear frame-rate scaling; added Xbox-specific HDR 4K validation.
Stream Page & AppPage
Pages/StreamPage.xaml, Pages/StreamPage.xaml.h, Pages/StreamPage.xaml.cpp, Pages/AppPage.xaml.cpp
Removed frame-queue size controls; removed increaseFrameDropTarget/decreaseFrameDropTarget handlers; added OnNavigatedTo VSYNC control based on FPS (disable if FPS > 60); removed forceTearing/enableVsync config assignments.
Configuration & State
State/MoonlightHost.h, State/StreamConfiguration.h, State/ApplicationState.cpp, State/MoonlightClient.cpp
Removed ForceTearing and EnableVsync properties from MoonlightHost; removed forceTearing from StreamConfiguration; removed force_tearing/enable_vsync serialization; updated MoonlightClient to use FPS-driven refresh rate calculation for Microsoft devices with FFMpegDecoder singleton.
Utility & Common
pch.h, Common/TextConsole.cpp, Utils/FloatBuffer.h, Utils/FloatBuffer.cpp
Added d3d11_4.h include; introduced QPC timing helpers (QpcFreq, QpcNow, QpcToMs/MsToQpc); added FRAME_QUEUE_VERBOSE logging; changed FloatBuffer::copyInto return type to int; added TextConsole logging output.
Build & Configuration
moonlight-xbox-dx.vcxproj, moonlight-xbox-dx.vcxproj.filters, .clang-format, .gitmodules, vcpkg.json
Added Pacer and DirectXHelper to build; enabled debug info for Release
Submodule Updates
third_party/moonlight-common-c, vcpkg
Updated third_party/moonlight-common-c commit reference; updated vcpkg commit reference.

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main Thread
    participant Pacer as Pacer (Thread)
    participant BackPacer as BackPacer (Thread)
    participant Decoder as FFMpegDecoder
    participant Render as VideoRenderer
    participant Device as DeviceResources
    
    Main->>Pacer: init(res, fps, refreshRate)
    Pacer->>Pacer: Start vsync & backPacer threads
    
    Decoder->>Pacer: submitFrame(AVFrame)
    Pacer->>Pacer: Enqueue to pacing queue
    
    BackPacer->>Device: waitForVBlank()
    Device-->>BackPacer: VSYNC signal
    BackPacer->>Pacer: handleVsync(deadline)
    Pacer->>Pacer: Apply frame drop logic, move to render queue
    
    Main->>Pacer: waitForFrame()
    Pacer-->>Main: Frame ready (or wait)
    
    Main->>Pacer: renderOnMainThread(sceneRenderer)
    Pacer->>Pacer: frontPacer() - timing/drop checks
    Pacer->>Render: Process frame from render queue
    Render->>Device: Present() via D3D
    Device-->>Render: Present complete
    Pacer->>Pacer: afterPresent(presentTime) - drift tracking
    Pacer-->>Main: render() complete
    
    Main->>Pacer: deinit()
    Pacer->>Pacer: Stop threads, drain queues, free resources
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas requiring extra attention:

  • Streaming/Pacer.cpp & Pacer.h: Complex multi-threaded frame-pacing orchestration with VSYNC emulation, queue management, and timing synchronization. Requires careful review of thread safety (mutexes, condition variables), QPC arithmetic, and state transitions.
  • Streaming/FFmpegDecoder.cpp & FFmpegDecoder.h: Substantial refactoring from legacy API to singleton with new SubmitDecodeUnit flow, frame metadata attachment, and decoder initialization sequence. Verify FFmpeg error handling and resource lifecycle.
  • Streaming/moonlight_xbox_dxMain.cpp & moonlight_xbox_dxMain.h: Critical render-loop integration with Pacer and input thread pacing. Verify thread priorities, frame synchronization, and resource cleanup.
  • Common/DeviceResources.cpp & DeviceResources.h: VSYNC/tearing behavior inversion (disabled by default), feature level upgrade, frame latency waitable. Verify DirectX API correctness and Xbox-specific paths.
  • State/MoonlightClient.cpp: FPS-driven refresh rate calculation with Microsoft device-specific logic and singleton FFMpegDecoder initialization. Verify fps-to-X100 mapping and config propagation.
  • Pages/HostSettingsPage.xaml.cpp: New getDefaultBitrate() with non-linear FPS scaling and resolution table interpolation. Verify bitrate calculations and UI responsiveness on resolution/FPS changes.
  • Plot/PlotDesc.h & Stats.cpp/Stats.h: Metrics renaming (QUEUED_FRAMES → VSYNC_INTERVAL) and new pacer/present-pacing stats. Verify plotting data flow and stat accumulation logic.

Possibly related PRs

Poem

🐰 Frames now leap through VSYNC gates,
With Pacer's threads that synchronize their fates,
No more forced tears or tangled queues,
Just silky-smooth 120 Hz views!
Frame latency awaits—
Quality accelerates! 🎬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.85% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "120fps frame pacing, async video decoder" directly corresponds to the two primary architectural changes in the changeset. The raw summary shows extensive new files implementing frame pacing (Pacer.h, Pacer.cpp, PacerRational.h) and substantial refactoring of FFMpegDecoder to use a singleton pattern with async initialization. All supporting infrastructure changes across DeviceResources, VideoRenderer, Stats, and UI components serve to enable these two main features. The title is specific, concise, and clearly communicates the primary technical focus without vagueness or noise.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Streaming/LogRenderer.cpp (1)

64-74: Consider adapting warning console font size to display resolution.

The main console adapts its font size based on display height (12pt for ≤1440p, 24pt for 4K), but the warning console always uses 24pt. This creates a visual inconsistency where the warning text may appear disproportionately large on lower-resolution displays.

     const wchar_t* font = L"Assets\\Font\\ModeSeven-24.spritefont"; // sized for 4K
+    const wchar_t* warningFont = font;
     if (m_displayHeight <= 1440) {
         font = L"Assets\\Font\\ModeSeven-12.spritefont"; // for 1080p & 1440p
+        warningFont = font;
     }
 
     m_console->RestoreDevice(m_deviceResources->GetD3DDeviceContext(), font);
-    m_warningConsole->RestoreDevice(m_deviceResources->GetD3DDeviceContext(), L"Assets\\Font\\ModeSeven-24.spritefont");
+    m_warningConsole->RestoreDevice(m_deviceResources->GetD3DDeviceContext(), warningFont);
Streaming/moonlight_xbox_dxMain.cpp (1)

270-279: Wrong key released: Menu maps to Enter, but KeyUp uses Right

The release path should send KeyUp(Enter) to mirror KeyDown(Enter).

Apply this diff:

-            else if (isPressed(previousReading[i].Buttons, GamepadButtons::Menu)) {
-                moonlightClient->KeyUp((unsigned short)Windows::System::VirtualKey::Right, 0);
-            }
+            else if (isPressed(previousReading[i].Buttons, GamepadButtons::Menu)) {
+                moonlightClient->KeyUp((unsigned short)Windows::System::VirtualKey::Enter, 0);
+            }
🧹 Nitpick comments (35)
Common/TextConsole.cpp (1)

26-26: Consider removing unused namespace declaration.

The moonlight_xbox_dx namespace is declared but no symbols from it are used in this file. Consider removing it to keep the code clean.

-using namespace moonlight_xbox_dx;
Streaming/LogRenderer.cpp (2)

33-46: Optimize static warning message updates.

The warning message is rewritten every second, even though it's static content. Consider writing it once when logs become visible (in ToggleVisible) instead of every update cycle.

Modify the Update method to only clear and update the main console:

     if (m_visible && timer.GetTotalSeconds() - lastUpdateSeconds >= 1.0) {
         m_console->Clear();
-        m_warningConsole->Clear();
 
         Utils::logMutex.lock();
         std::vector<std::wstring> lines = Utils::GetLogLines();
         for (std::wstring line : lines) {
             m_console->Write(line.c_str());
         }
         Utils::logMutex.unlock();
 
-        m_warningConsole->Write(L"Warning: Viewing logs reduces performance");
-
         lastUpdateSeconds = timer.GetTotalSeconds();
     }

Then write the warning message once in ToggleVisible:

void LogRenderer::ToggleVisible() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_visible = !m_visible;
    if (m_visible) {
        m_warningConsole->Clear();
        m_warningConsole->Write(L"Warning: Viewing logs reduces performance");
    }
}

77-87: Consider documenting or extracting magic numbers.

The hardcoded values 44 and 20 appear to represent vertical margins or spacing but are not documented. Consider adding comments or extracting them as named constants to improve readability.

+    // Reserve bottom area for warning console: 44px from bottom minus 20px margin
+    constexpr int CONSOLE_BOTTOM_MARGIN = 44;
+    constexpr int WARNING_TOP_MARGIN = 20;
+
     // The size of our text area (left, top, right, bottom)
-    RECT size = {m_displayWidth * 0.5, 0, m_displayWidth - 20, m_displayHeight - 44};
+    RECT size = {m_displayWidth * 0.5, 0, m_displayWidth - WARNING_TOP_MARGIN, m_displayHeight - CONSOLE_BOTTOM_MARGIN};
 
     m_console->SetWindow(size);
 
     // The size of our text area (left, top, right, bottom)
-    RECT warningSize = {size.left, m_displayHeight - 44, size.right, m_displayHeight - 20};
+    RECT warningSize = {size.left, m_displayHeight - CONSOLE_BOTTOM_MARGIN, size.right, m_displayHeight - WARNING_TOP_MARGIN};
     m_warningConsole->SetWindow(warningSize);
Pages/HostSettingsPage.xaml.h (1)

106-106: Make getDefaultBitrate static/const and align naming

It doesn’t use instance state. Consider:

  • Make it static (or at least const).
  • Rename to GetDefaultBitrate for consistency with surrounding PascalCase methods.
-    int getDefaultBitrate(int width, int height, int fps);
+    static int GetDefaultBitrate(int width, int height, int fps) noexcept;
.clang-format (1)

5-5: Format policy change: ensure repo-wide consistency

Brace and constructor-initializer rules changed. Re-run formatting across the tree to avoid mixed styles in future diffs.

Please run your usual clang-format step locally and commit any resultant changes.

Also applies to: 15-16

Streaming/PacerRational.h (1)

5-10: Tighten includes and guard MSVC intrinsics

_immintrin.h isn’t used here; keep <intrin.h>. Also guard MSVC-specific intrinsics to avoid toolchain issues.

-#include <immintrin.h>
-#include <intrin.h>
-#pragma intrinsic(_umul128, _udiv128)
+#include <intrin.h>
+#if defined(_MSC_VER)
+#pragma intrinsic(_umul128, _udiv128)
+#endif
.gitmodules (1)

3-3: Submodule is properly pinned; document fork rationale

The submodule commit is pinned (4372ab392c3532adabc3b087180d79cd011b762f), which mitigates supply-chain risk. However, using a personal fork introduces an availability risk. Consider:

  • Adding a comment in .gitmodules or the PR description explaining why the personal fork is necessary instead of the upstream
  • Optionally setting up a mirror or sync mechanism to safeguard against fork unavailability
Utils/FloatBuffer.cpp (1)

56-83: Make narrowing conversion explicit

The function returns int but outLen is std::size_t, causing an implicit narrowing conversion at line 83. While capacity_ is validated as a power of two, there is no upper bound check to guarantee it fits in int. Make this explicit and document the assumption.

- return outLen;
+ // outLen ≤ capacity_; capacity_ is a power of two validated at construction.
+ return static_cast<int>(outLen);

Optional: add defensive assertions.

+ assert(outBuffer != nullptr);
+ assert(outSize > 0);
Streaming/VideoRenderer.h (2)

33-35: Minimize public API surface: make EnsureYuvTargets private.

It looks like an internal helper (called from Render). Keeping it public needlessly expands the surface area. Move it to private unless external callers need it.

-   void EnsureYuvTargets(ID3D11Device* dev, DXGI_FORMAT fmt, UINT w, UINT h);
+private:
+   void EnsureYuvTargets(ID3D11Device* dev, DXGI_FORMAT fmt, UINT w, UINT h);
+public:

57-62: YUV SRVs state — good, but ensure header includes for types.

Members look correct for NV12/P010 sampling. Since this header uses AVFrame and AVColorTransferCharacteristic, explicitly include avutil headers to guarantee definitions in all TU’s and speed up builds:

 extern "C" {
-#include <libavcodec/avcodec.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/frame.h>
+#include <libavutil/pixfmt.h>
 }
Pages/HostSettingsPage.xaml.cpp (3)

123-131: Resolution change: also compare height.

You only compare width before recomputing bitrate. Consider width or height change.

- if (selectedResolution->Width != host->Resolution->Width) {
+ if (selectedResolution->Width != host->Resolution->Width ||
+     selectedResolution->Height != host->Resolution->Height) {

180-180: Minor: trailing whitespace/blank lines.

Nit: stray trailing markers after unsubscribe; no behavior change.


182-229: Include math header for sqrtf/lround and document units.

getDefaultBitrate uses std::sqrtf and std::lround. Ensure is included (via pch or here) and consider a brief comment clarifying return units (kbps).

-#include "Utils.hpp"
+#include "Utils.hpp"
+#include <cmath> // sqrtf, lround

Optionally rename to getDefaultBitrateKbps or document that multiplier 1000 yields kbps.

Plot/PlotDesc.h (1)

35-45: Guard enum/table drift at compile time

kPlotDescs must always match PlotType. Add a static_assert to catch future mismatches at build time.

Apply this diff just below the array:

 inline constexpr std::array<PlotDesc, PlotCount> kPlotDescs = {{
     {"Frametime",                      PLOT_LABEL_MIN_MAX_AVG, "ms", -0.1f, 50.0f, 0.0f, 41.0f},
     {"Host Frametime",                 PLOT_LABEL_MIN_MAX_AVG, "ms", -0.1f, 50.0f, 0.0f, 41.0f},
     {"Vsync interval",                 PLOT_LABEL_MIN_MAX_AVG, "ms", -0.1f, 50.0f, 0.0f, 0.0f},
     {"Dropped frames (network)",       PLOT_LABEL_TOTAL_INT,     "", -1.0f, 3.0f, 0.0f, 0.0f},
     {"Dropped frames (pacing)",        PLOT_LABEL_TOTAL_INT,     "", -1.0f, 3.0f, 0.0f, 0.0f},
     {"Present to display latency",     PLOT_LABEL_MIN_MAX_AVG, "ms", -10.0f, 25.0f, 0.0f, 0.0f},
     {"Graph overhead",                 PLOT_LABEL_MIN_MAX_AVG, "ms", -0.1f, 6.0f, 0.0f, 0.0f},
 	{"Etc...",                         PLOT_LABEL_MIN_MAX_AVG, "ms", -0.1f, 50.0f, 0.0f, 49.9f},
 }};
+
+static_assert(kPlotDescs.size() == PlotCount, "Plot descriptors out of sync with PlotType enum");
Streaming/moonlight_xbox_dxMain.cpp (2)

145-149: 2 kHz comment vs 2 ms period mismatch

2000 microseconds = 0.002s = 500 Hz. Either update the comment to 500 Hz or change period to 500 microseconds for 2 kHz.

Option A (fix comment):

-            // Target period = 2000hz
+            // Target period = 500 Hz (2000 us)

Option B (keep 2 kHz):

-            const auto period = std::chrono::microseconds(2000);
+            const auto period = std::chrono::microseconds(500);

1-10: Ensure Pacer header is included explicitly

This file uses Pacer::instance() but doesn’t include Pacer.h here. If not pulled via another header, add the include to avoid fragile transitive dependencies.

 #include <Pages/StreamPage.xaml.h>
+#include <Streaming\Pacer.h>
 #include <Streaming\FFMpegDecoder.h>
State/MoonlightClient.cpp (1)

222-245: Round clientRefreshRateX100 to nearest integer

(int)(rr*100.0) can under/overflow by truncation (e.g., 59.94→5993). Use lround to get 5994 reliably.

Apply this diff (add if not already included):

-        case 120:
-            config.clientRefreshRateX100 = (int)(rr * 100.0);
+        case 120:
+            config.clientRefreshRateX100 = (int)std::lround(rr * 100.0);
             break;
@@
-            } else {
-                config.clientRefreshRateX100 = (int)(rr * 100.0);
+            } else {
+                config.clientRefreshRateX100 = (int)std::lround(rr * 100.0);
             }

And near the includes:

 #include <atomic>
+#include <cmath>
Streaming/StatsRenderer.cpp (2)

66-88: Minor: avoid unused variable warning

ImGuiIO& io is unused; either remove or mark unused.

-    ImGuiIO &io = ImGui::GetIO();
+    (void)ImGui::GetIO();

70-79: Optional: prefer RAII over raw malloc for buffers

Use std::array<float, 512> or std::vector to avoid manual allocation and ease lifetime management.

Common/DeviceResources.h (2)

90-92: Manage HANDLE lifetime with RAII to prevent leaks across swap-chain recreation.

Wrap m_frameLatencyWaitable in a smart handle (e.g., wil::unique_handle) and close before creating a new swap chain.

-        HANDLE                                          m_frameLatencyWaitable;
+        wil::unique_handle                              m_frameLatencyWaitable;

And reset appropriately when (re)creating the swap chain and on HandleDeviceLost().


34-34: Consider const correctness for isXbox().

Method doesn’t mutate state; mark it const to improve API clarity.

-        bool isXbox();
+        bool isXbox() const;
State/Stats.cpp (2)

94-100: Make RTP delta wrap-safe for long sessions.

lastHostPts is uint32_t and will wrap ~13.3 hours. Compute delta in uint32_t to leverage modular arithmetic.

-    static uint32_t lastHostPts = 0;
-    if (lastHostPts > 0) {
-        ImGuiPlots::instance().observeFloat(PLOT_HOST_FRAMETIME, (float)((decodeUnit->rtpTimestamp - lastHostPts) / 90.0f));
-    }
-    lastHostPts = decodeUnit->rtpTimestamp;
+    static uint32_t lastHostPts = 0;
+    if (lastHostPts != 0) {
+        const uint32_t delta90k = (uint32_t)(decodeUnit->rtpTimestamp - lastHostPts); // wrap-safe
+        ImGuiPlots::instance().observeFloat(PLOT_HOST_FRAMETIME, (float)(delta90k / 90.0f));
+    }
+    lastHostPts = (uint32_t)decodeUnit->rtpTimestamp;

262-275: Fix snprintf bound check type-safety.

ret is int; length - offset is size_t. Cast to avoid signed/unsigned pitfalls.

-        if (ret < 0 || ret >= length - offset) {
+        if (ret < 0 || (size_t)ret >= (length - offset)) {
             Utils::Log("Error: stringifyVideoStats length overflow\n");
             return;
         }

Repeat for similar checks below.

Streaming/Pacer.h (1)

43-44: Clarify waitForVBlank preconditions and stop behavior.

IDXGIOutput::WaitForVBlank blocks until next vblank; it can’t be interrupted by m_Stopping. Document that deinit() will only return after the next vblank and ensure m_dxgiOutput remains valid during teardown.

  • Add a comment to waitForVBlank() with the above contract.
  • Optionally add a fast-path using FrameLatencyWaitable when DXGIOutput is unavailable.
Common/DirectXHelper.cpp (1)

35-45: Avoid busy-waiting + accidental pipeline flush; bound polling and skip NaNs in avg.

  • Use D3D11_ASYNC_GETDATA_DONOTFLUSH to prevent implicit flushes.
  • Poll with a small spin/yield or bail if not ready.
  • When computing average, ignore NaNs.
-        while (context->GetData(m_disjointQuery[ReadQuery].Get(), &disjointData, sizeof(disjointData), 0) != S_OK);
+        while (context->GetData(m_disjointQuery[ReadQuery].Get(), &disjointData, sizeof(disjointData), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_FALSE) { /* brief spin or yield */ }

-            while (context->GetData(m_startTimestampQuery[ReadQuery].Get(), &start, sizeof(start), 0) != S_OK);
+            while (context->GetData(m_startTimestampQuery[ReadQuery].Get(), &start, sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_FALSE) { /* brief spin or yield */ }

-            while (context->GetData(m_endTimestampQuery[ReadQuery].Get(), &end, sizeof(end), 0) != S_OK);
+            while (context->GetData(m_endTimestampQuery[ReadQuery].Get(), &end, sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_FALSE) { /* brief spin or yield */ }
@@
-        m_processingTimeHistory[m_processingTimeHistoryIndex] = m_processingTimeMs;
+        m_processingTimeHistory[m_processingTimeHistoryIndex] = m_processingTimeMs;
         m_processingTimeHistoryIndex = (m_processingTimeHistoryIndex + 1) % TimeHistoryCount;
-
-        m_processingTimeAvgMs = std::accumulate(m_processingTimeHistory, m_processingTimeHistory + TimeHistoryCount, 0.0f) / static_cast<float>(TimeHistoryCount);
+        // Compute avg ignoring NaNs
+        float sum = 0.0f; int cnt = 0;
+        for (float v : m_processingTimeHistory) { if (!std::isnan(v)) { sum += v; ++cnt; } }
+        m_processingTimeAvgMs = (cnt > 0) ? (sum / cnt) : std::numeric_limits<float>::quiet_NaN();

Also applies to: 67-71

Common/DeviceResources.cpp (2)

468-471: SetVsync() doesn’t recreate swap chain; Present() recovers via device-lost.

Works but is heavy. Consider recreating the swapchain when m_enableVsync changes to avoid a device-loss cycle and logs.


611-631: Consolidate Xbox detection helpers.

You have both is_running_on_xbox() and DeviceResources::isXbox(). Keep one and reuse to avoid divergence.

Also applies to: 633-658

Streaming/VideoRenderer.cpp (3)

511-517: 10‑bit offsets applied only for Rec.2020.

If hosts send 10‑bit SDR Rec.709, k_Offsets_10bit_* may also be needed. If that’s not expected in practice, ignore; otherwise add a 10‑bit path for 709.


542-561: Use RAII for decoder mutex in SetHDR().

Replace manual lock/unlock with scoped_lock to avoid accidental leaks on early returns/exceptions.

-    FFMpegDecoder::instance().mutex.lock();
+    std::scoped_lock<std::recursive_mutex> lk(FFMpegDecoder::instance().mutex);
...
-    FFMpegDecoder::instance().mutex.unlock();

144-146: Remove unused global.

renderedOneFrame is not used.

-bool renderedOneFrame = false;
Streaming/FFmpegDecoder.h (2)

39-41: Public recursive_mutex is a sharp edge.

Prefer encapsulating internal synchronization or exposing narrower ops that perform their own locking. If external locking remains, document invariants.


6-15: Include cycle claim is incorrect; VideoRenderer.h include appears unused in header.

No include cycle exists—VideoRenderer.h does not include FFmpegDecoder.h. Additionally, FFmpegDecoder class does not use any VideoRenderer types or instantiate VideoRenderer in the header, making the #include "VideoRenderer.h" include potentially unnecessary.

However, the suggested forward declarations for FFmpeg types won't work as written: types like AVCodec, AVCodecContext, AVHWDeviceContext, and AVD3D11VADeviceContext are used as member pointers in the class (requiring full type definitions). Only types used in function signatures (not member variables) can be forward-declared.

If VideoRenderer.h is only used in the .cpp file, moving it there is valid. Otherwise, verify its actual usage before refactoring.

Streaming/FFmpegDecoder.cpp (2)

162-169: Multithread protection: check QueryInterface failure path.

If QueryInterface fails, log once so we know we’re running without MT-protection.

-    if (SUCCEEDED(hr)) {
+    if (SUCCEEDED(hr)) {
         pMultithread->SetMultithreadProtected(TRUE);
         pMultithread->Release();
     }
+    else {
+        Utils::Log("Warning: ID3D11Multithread not available; proceeding without SetMultithreadProtected.\n");
+    }

298-301: Global ffmpeg log level.

Dropping to AV_LOG_QUIET globally may hinder troubleshooting. Consider AV_LOG_ERROR after first success instead of QUIET.

-    if (av_log_get_level() > 0) {
-        av_log_set_level(AV_LOG_QUIET);
-    }
+    if (av_log_get_level() > AV_LOG_ERROR) {
+        av_log_set_level(AV_LOG_ERROR);
+    }
Streaming/Pacer.cpp (1)

133-161: Initialize and log with explicit newlines, minor nit.

Log in init prints without newline; add for consistency.

-    Utils::Logf("Pacer: init target %.2f Hz with %d FPS stream",
+    Utils::Logf("Pacer: init target %.2f Hz with %d FPS stream\n",
                 m_RefreshRate, m_StreamFps);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f20c78 and 48d6137.

⛔ Files ignored due to path filters (1)
  • Assets/Shader/d3d11_bt2020lim_pixel.hlsl is excluded by !**/*.hlsl
📒 Files selected for processing (42)
  • .clang-format (2 hunks)
  • .gitmodules (1 hunks)
  • Common/DeviceResources.cpp (14 hunks)
  • Common/DeviceResources.h (3 hunks)
  • Common/DirectXHelper.cpp (1 hunks)
  • Common/DirectXHelper.h (1 hunks)
  • Common/TextConsole.cpp (2 hunks)
  • Pages/AppPage.xaml.cpp (0 hunks)
  • Pages/HostSettingsPage.xaml (2 hunks)
  • Pages/HostSettingsPage.xaml.cpp (4 hunks)
  • Pages/HostSettingsPage.xaml.h (1 hunks)
  • Pages/StreamPage.xaml (0 hunks)
  • Pages/StreamPage.xaml.cpp (1 hunks)
  • Pages/StreamPage.xaml.h (0 hunks)
  • Plot/ImGuiPlots.cpp (1 hunks)
  • Plot/PlotDesc.h (2 hunks)
  • State/ApplicationState.cpp (0 hunks)
  • State/MoonlightClient.cpp (2 hunks)
  • State/MoonlightHost.h (0 hunks)
  • State/Stats.cpp (9 hunks)
  • State/Stats.h (2 hunks)
  • State/StreamConfiguration.h (0 hunks)
  • Streaming/FFmpegDecoder.cpp (4 hunks)
  • Streaming/FFmpegDecoder.h (1 hunks)
  • Streaming/LogRenderer.cpp (5 hunks)
  • Streaming/LogRenderer.h (1 hunks)
  • Streaming/Pacer.cpp (1 hunks)
  • Streaming/Pacer.h (1 hunks)
  • Streaming/PacerRational.h (1 hunks)
  • Streaming/StatsRenderer.cpp (8 hunks)
  • Streaming/VideoRenderer.cpp (9 hunks)
  • Streaming/VideoRenderer.h (3 hunks)
  • Streaming/moonlight_xbox_dxMain.cpp (6 hunks)
  • Streaming/moonlight_xbox_dxMain.h (2 hunks)
  • Utils/FloatBuffer.cpp (1 hunks)
  • Utils/FloatBuffer.h (1 hunks)
  • moonlight-xbox-dx.vcxproj (4 hunks)
  • moonlight-xbox-dx.vcxproj.filters (3 hunks)
  • pch.h (3 hunks)
  • third_party/moonlight-common-c (1 hunks)
  • vcpkg (1 hunks)
  • vcpkg.json (1 hunks)
💤 Files with no reviewable changes (6)
  • State/StreamConfiguration.h
  • Pages/StreamPage.xaml.h
  • Pages/StreamPage.xaml
  • State/MoonlightHost.h
  • State/ApplicationState.cpp
  • Pages/AppPage.xaml.cpp
🧰 Additional context used
🧬 Code graph analysis (21)
Utils/FloatBuffer.h (1)
Utils/FloatBuffer.cpp (2)
  • copyInto (56-83)
  • copyInto (56-56)
Streaming/LogRenderer.cpp (2)
Streaming/moonlight_xbox_dxMain.cpp (2)
  • CreateWindowSizeDependentResources (93-98)
  • CreateWindowSizeDependentResources (93-93)
Streaming/StatsRenderer.cpp (4)
  • CreateWindowSizeDependentResources (193-214)
  • CreateWindowSizeDependentResources (193-193)
  • ReleaseDeviceDependentResources (216-218)
  • ReleaseDeviceDependentResources (216-216)
State/MoonlightClient.cpp (3)
Utils.cpp (2)
  • Logf (90-99)
  • Logf (90-90)
Streaming/FFmpegDecoder.cpp (4)
  • instance (42-45)
  • instance (42-42)
  • getDecoder (319-328)
  • getDecoder (319-319)
Streaming/AudioPlayer.cpp (2)
  • getDecoder (49-60)
  • getDecoder (49-49)
State/Stats.h (1)
State/Stats.cpp (4)
  • SubmitPacerTime (117-125)
  • SubmitPacerTime (117-117)
  • SubmitPresentPacing (128-131)
  • SubmitPresentPacing (128-128)
Pages/HostSettingsPage.xaml.h (1)
Pages/HostSettingsPage.xaml.cpp (2)
  • getDefaultBitrate (182-229)
  • getDefaultBitrate (182-182)
Streaming/VideoRenderer.h (1)
Streaming/VideoRenderer.cpp (4)
  • EnsureYuvTargets (106-142)
  • EnsureYuvTargets (106-106)
  • Render (146-211)
  • Render (146-146)
Streaming/LogRenderer.h (2)
Common/TextConsole.h (2)
  • DX (25-88)
  • TextConsole (27-87)
Common/TextConsole.cpp (2)
  • TextConsole (28-36)
  • TextConsole (40-40)
Streaming/Pacer.h (1)
Streaming/Pacer.cpp (33)
  • Pacer (80-91)
  • instance (75-78)
  • instance (75-75)
  • deinit (93-131)
  • deinit (93-93)
  • init (133-161)
  • init (133-133)
  • waitForFrame (508-527)
  • waitForFrame (508-508)
  • renderOnMainThread (530-549)
  • renderOnMainThread (530-530)
  • waitUntilPresentTarget (622-628)
  • waitUntilPresentTarget (622-622)
  • afterPresent (631-650)
  • afterPresent (631-631)
  • submitFrame (674-685)
  • submitFrame (674-674)
  • vsyncEmulator (183-278)
  • vsyncEmulator (183-183)
  • vsyncHardware (280-359)
  • vsyncHardware (280-280)
  • backPacer (361-395)
  • backPacer (361-361)
  • handleVsync (397-493)
  • handleVsync (397-397)
  • enqueueFrameForRenderingAndUnlock (495-504)
  • enqueueFrameForRenderingAndUnlock (495-495)
  • frontPacer (551-619)
  • frontPacer (551-551)
  • dropFrameForEnqueue (654-672)
  • dropFrameForEnqueue (654-654)
  • waitForVBlank (689-692)
  • waitForVBlank (689-689)
Streaming/StatsRenderer.cpp (4)
Streaming/LogRenderer.cpp (8)
  • CreateDeviceDependentResources (60-75)
  • CreateDeviceDependentResources (60-60)
  • Render (51-58)
  • Render (51-51)
  • CreateWindowSizeDependentResources (77-87)
  • CreateWindowSizeDependentResources (77-77)
  • ToggleVisible (95-98)
  • ToggleVisible (95-95)
Common/TextConsole.cpp (2)
  • Render (54-85)
  • Render (54-54)
pch.h (2)
  • QpcNow (55-59)
  • QpcToMs (82-84)
Plot/ImGuiPlots.cpp (2)
  • instance (14-18)
  • instance (14-14)
Common/DeviceResources.cpp (3)
Common/DirectXHelper.h (2)
  • ThrowIfFailed (8-19)
  • ThrowIfFailed (21-32)
Utils.cpp (2)
  • Logf (90-99)
  • Logf (90-90)
Utils.hpp (1)
  • Logf (15-15)
Streaming/VideoRenderer.cpp (3)
Common/DirectXHelper.h (2)
  • ThrowIfFailed (8-19)
  • ThrowIfFailed (21-32)
Utils.cpp (6)
  • Logf (90-99)
  • Logf (90-90)
  • Log (57-82)
  • Log (57-57)
  • Log (84-88)
  • Log (84-84)
Streaming/FFmpegDecoder.cpp (2)
  • instance (42-45)
  • instance (42-42)
Common/TextConsole.cpp (2)
Utils.cpp (2)
  • Logf (90-99)
  • Logf (90-90)
Utils.hpp (1)
  • Logf (15-15)
Common/DirectXHelper.cpp (1)
Common/DirectXHelper.h (1)
  • GpuPerformanceTimer (85-120)
Streaming/Pacer.cpp (6)
State/Stats.h (1)
  • moonlight_xbox_dx (47-87)
Streaming/VideoRenderer.h (1)
  • moonlight_xbox_dx (12-75)
Plot/ImGuiPlots.cpp (2)
  • instance (14-18)
  • instance (14-14)
Streaming/Pacer.h (1)
  • Pacer (14-68)
pch.h (7)
  • f (47-51)
  • QpcFreq (46-53)
  • UsToQpc (61-65)
  • QpcNow (55-59)
  • QpcToMs (82-84)
  • QpcToUs (67-76)
  • MsToQpc (86-89)
Streaming/PacerRational.h (1)
  • hzNum (13-48)
Common/DeviceResources.h (1)
Common/DeviceResources.cpp (14)
  • SetVsync (468-471)
  • SetVsync (468-468)
  • ValidateDevice (474-523)
  • ValidateDevice (474-474)
  • HandleDeviceLost (526-546)
  • HandleDeviceLost (526-526)
  • Present (565-609)
  • Present (565-565)
  • GetUWPPixelDimensions (660-681)
  • GetUWPPixelDimensions (660-660)
  • GetUWPRefreshRate (683-700)
  • GetUWPRefreshRate (683-683)
  • isXbox (619-630)
  • isXbox (619-619)
Common/DirectXHelper.h (4)
Common/DeviceResources.cpp (1)
  • DeviceResources (59-86)
Common/DeviceResources.h (2)
  • DeviceResources (16-135)
  • DX (6-136)
Common/DirectXHelper.cpp (13)
  • GpuPerformanceTimer (8-23)
  • StartTimerForFrame (25-77)
  • StartTimerForFrame (25-25)
  • EndTimerForFrame (79-84)
  • EndTimerForFrame (79-79)
  • GetFrameTime (86-89)
  • GetFrameTime (86-86)
  • GetAvgFrameTime (91-94)
  • GetAvgFrameTime (91-91)
  • GetMinFrameTime (96-99)
  • GetMinFrameTime (96-96)
  • GetMaxFrameTime (101-104)
  • GetMaxFrameTime (101-101)
Streaming/moonlight_xbox_dxMain.h (1)
  • DX (9-11)
Streaming/FFmpegDecoder.cpp (4)
State/Stats.h (1)
  • moonlight_xbox_dx (47-87)
Streaming/FFmpegDecoder.h (2)
  • moonlight_xbox_dx (26-57)
  • FFMpegDecoder (28-56)
Streaming/Pacer.cpp (2)
  • instance (75-78)
  • instance (75-75)
Utils.cpp (6)
  • Logf (90-99)
  • Logf (90-90)
  • Log (57-82)
  • Log (57-57)
  • Log (84-88)
  • Log (84-84)
Streaming/FFmpegDecoder.h (3)
Streaming/FFmpegDecoder.cpp (13)
  • FFMpegDecoder (47-59)
  • instance (42-45)
  • instance (42-42)
  • CompleteInitialization (78-81)
  • CompleteInitialization (78-78)
  • Init (83-181)
  • Init (83-83)
  • Cleanup (183-195)
  • Cleanup (183-183)
  • SubmitDecodeUnit (216-304)
  • SubmitDecodeUnit (216-216)
  • getDecoder (319-328)
  • getDecoder (319-319)
Streaming/Pacer.cpp (2)
  • instance (75-78)
  • instance (75-75)
Common/DeviceResources.h (2)
  • DX (6-136)
  • DeviceResources (16-135)
Streaming/moonlight_xbox_dxMain.cpp (5)
Utils.cpp (2)
  • Logf (90-99)
  • Logf (90-90)
Streaming/VideoRenderer.cpp (2)
  • Render (146-211)
  • Render (146-146)
Streaming/Pacer.cpp (4)
  • instance (75-78)
  • instance (75-75)
  • afterPresent (631-650)
  • afterPresent (631-631)
Plot/ImGuiPlots.cpp (2)
  • instance (14-18)
  • instance (14-14)
pch.h (2)
  • QpcNow (55-59)
  • QpcToMs (82-84)
Streaming/moonlight_xbox_dxMain.h (3)
Common/DirectXHelper.h (2)
  • DX (6-121)
  • GpuPerformanceTimer (85-120)
Common/DirectXHelper.cpp (1)
  • GpuPerformanceTimer (8-23)
Streaming/VideoRenderer.h (1)
  • VideoRenderer (23-74)
State/Stats.cpp (4)
Plot/ImGuiPlots.cpp (2)
  • instance (14-18)
  • instance (14-14)
Streaming/FFmpegDecoder.cpp (2)
  • instance (42-45)
  • instance (42-42)
pch.h (1)
  • QpcToUs (67-76)
Utils.cpp (4)
  • Log (57-82)
  • Log (57-57)
  • Log (84-88)
  • Log (84-84)
🪛 Clang (14.0.6)
Streaming/PacerRational.h

[error] 5-5: 'cstdint' file not found

(clang-diagnostic-error)

Streaming/Pacer.h

[error] 3-3: 'atomic' file not found

(clang-diagnostic-error)

Common/DirectXHelper.cpp

[error] 1-1: 'pch.h' file not found

(clang-diagnostic-error)

Streaming/Pacer.cpp

[error] 2-2: 'pch.h' file not found

(clang-diagnostic-error)

🔇 Additional comments (32)
Streaming/LogRenderer.h (1)

26-26: LGTM!

The addition of m_warningConsole as a std::unique_ptr follows the same pattern as the existing m_console member and ensures proper resource management.

Streaming/LogRenderer.cpp (3)

13-20: LGTM!

The warning console initialization is properly implemented with appropriate color settings for warnings.


54-57: LGTM!

Both consoles are properly rendered when visible.


89-93: LGTM!

Both consoles properly release their device resources during cleanup.

vcpkg (1)

1-1: Submodule pointer correctly aligns with vcpkg.json FFmpeg pinning.

Verification confirms the vcpkg submodule pointer update to 120deac3062162151622ca4860575a33844ba10b is intentional and properly configured. The vcpkg.json file explicitly sets this same commit hash as its builtin-baseline and pins FFmpeg to version 7.1.1 in the overrides section. The changes are correctly coordinated.

third_party/moonlight-common-c (1)

1-1: Verify the rationale for the submodule fork and URL change.

The submodule pointer is being updated to a fork (andygrundman/moonlight-common-c) instead of the official upstream. This introduces a maintenance dependency on an external fork.

Please clarify:

  1. What specific changes in the fork are required for this PR's frame pacing and decoder optimizations?
  2. Is this fork temporary or intended to be long-term?
  3. Are there plans to upstream these changes to the official repository?
  4. Will other developers working on this codebase need to maintain this fork, or will it be merged back to upstream?

Understanding these details will help assess the maintainability and long-term implications of this change.

Streaming/PacerRational.h (1)

37-48: Step logic looks solid

Fractional carry via accum/den is correct for 59.94/119.88 style rates. No changes requested.

Pages/HostSettingsPage.xaml.h (1)

99-99: FPS selector handler wiring confirmed; no manual unsubscription required

The handler is correctly wired in XAML (Pages/HostSettingsPage.xaml:56). In UWP, XAML-wired event handlers are managed by the framework and don't require explicit unsubscription since the event source (FPSSelector ComboBox) is destroyed with the page. No handler leak risk.

moonlight-xbox-dx.vcxproj.filters (2)

383-388: New sources added under correct filter — LGTM.

Streaming\Pacer.cpp and Common\DirectXHelper.cpp are properly placed under “Source Files”.


472-480: Header additions wired correctly.

Pacer.h, PacerCompat.h, and PacerRational.h are properly included under “Header Files”.

Pages/HostSettingsPage.xaml (3)

56-56: One-way SelectedItem + handler is fine here.

Binding SelectedItem OneWay and using SelectionChanged to update Host.FPS is acceptable given you recompute bitrate first.

Ensure Host.FPS updates programmatically still reflect in the ComboBox as expected.


81-84: Hiding V-Sync controls — comment OK.

Commenting out the V-Sync UI with a clarifying note is fine given automatic handling.


85-99: Stats/graphs bindings — LGTM.

Graphs checkbox correctly gated by EnableStats and bound TwoWay.

Streaming/VideoRenderer.h (2)

8-10: Correct C linkage for FFmpeg headers — LGTM.

Including libavcodec under extern "C" avoids mangling issues.


69-70: Atomic loading flag — LGTM.

Switch to std::atomic is appropriate; use acquire/release as you did in cpp.

Pages/HostSettingsPage.xaml.cpp (1)

81-87: Codec gating for Xbox One — LGTM.

Disabling codec selection on OG Xbox One is a sensible UX constraint.

vcpkg.json (2)

3-3: ****

The CI workflow (.github/workflows/msbuild.yml) downloads pre-built packages from a release archive and does not use vcpkg's manifest resolution system. The builtin-baseline in vcpkg.json is not referenced or used by CI, so baseline alignment with the CI vcpkg instance is not a concern for this build pipeline. If builtin-baseline is intended for local development, it's orthogonal to the CI configuration shown here.

Likely an incorrect or invalid review comment.


25-29: No issues found — FFmpeg 7.1.1 is available for x64-uwp.

vcpkg includes FFmpeg v7.1.1 and supports installation for the x64-uwp triplet. The version pinning in vcpkg.json is safe and properly supported.

Streaming/moonlight_xbox_dxMain.h (2)

9-11: Forward declaration OK

Minimizes header coupling; no issues.


52-52: No actionable issues found—original review comment is unfounded.

The verification confirms a single owner (moonlight_xbox_dxMain via make_shared), no stored references in Pacer, and no circular dependencies. Pacer receives the shared_ptr by reference but does not store it, and VideoRenderer contains no back-references. While passing shared_ptr by reference to temporary consumers is unconventional style, it poses no correctness risk—no lifetime extension, no cycles, no hidden multiple owners.

Likely an incorrect or invalid review comment.

Pages/StreamPage.xaml.cpp (1)

245-249: BackRequested unsubscription on unload

Good cleanup; prevents handler leaks.

State/Stats.h (1)

35-38: Review comment is incorrect—zero-initialization and aggregation are already implemented

The codebase already properly handles all concerns raised:

  • Zero-initialization: Constructor at Stats.cpp:9 uses ZeroMemory() to initialize m_ActiveWndVideoStats, m_LastWndVideoStats, and m_GlobalVideoStats
  • Aggregation: Both totalPacerTimeUs (line 144) and totalPresentDisplayMs (line 146) are already aggregated in addVideoStats()
  • Formatting: Both fields are already used in formatVideoStats() (lines 349-350)

No action is needed; the code is correct as-is.

Likely an incorrect or invalid review comment.

Plot/ImGuiPlots.cpp (1)

20-31: Initializer and array definitions verified—code is correct

All three new plots (PLOT_VSYNC_INTERVAL, PLOT_PRESENT_PACING, PLOT_ETC) are properly defined in the enum, have matching entries in kPlotDescs (size = PlotCount = 8), and the initializer correctly uses all 8 plots without out-of-range indexing.

moonlight-xbox-dx.vcxproj (4)

260-261: Confirm Release PDB intent

GenerateDebugInformation=true in Release increases package size; ensure Store packaging and perf goals are OK. If not required on retail builds, consider a dedicated “Symbols” config instead.


348-351: Good: Pacer headers included in project

Header adds align with new pacing system; no action needed.


386-387: Good: Adds DirectXHelper.cpp to build

Required for GpuPerformanceTimer and helper APIs referenced elsewhere.


485-486: Good: Adds Pacer.cpp to build

Ensures singleton and pacing logic is linked.

State/MoonlightClient.cpp (1)

294-296: Decoder init path LGTM

Singleton init + getDecoder usage aligns with the new async decoder design.

Streaming/StatsRenderer.cpp (1)

176-177: Good: Overhead measured via QPC delta

This complements the new Pacer plots cleanly.

Common/DeviceResources.h (1)

25-25: Unify VSync setters to avoid duplication.

Verification confirms SetVsync is the primary public interface (used in StreamPage.xaml.cpp), while SetEnableVsync is unused elsewhere. Both directly mutate m_enableVsync. Refactor SetEnableVsync to delegate to SetVsync for a single source of truth.

-        void                        SetEnableVsync(bool ev)                 { m_enableVsync = ev; }
+        void                        SetEnableVsync(bool ev)                 { SetVsync(ev); }
State/Stats.cpp (1)

115-125: All verification concerns confirmed — no issues found.

✓ Zero-initialization verified: All three VIDEO_STATS instances (m_ActiveWndVideoStats, m_LastWndVideoStats, m_GlobalVideoStats) are properly zero-initialized via ZeroMemory in the constructor (State/Stats.cpp:13-15) and on window rotation (State/Stats.cpp:47).

✓ New fields confirmed in struct: Both totalPacerTimeUs (uint64_t) and totalPresentDisplayMs (double) are declared in the VIDEO_STATS typedef (State/Stats.h:35, 37).

✓ Serialization/persistence: Not applicable — no stats persistence mechanisms exist in the codebase. Stats are only formatted for display output via formatVideoStats.

The pacer/present metrics implementation is sound. New fields are properly initialized, consistently accumulated with += operations, and correctly used in reporting.

Streaming/VideoRenderer.cpp (1)

106-142: SRV format mappings for NV12/P010 are correct; shader code not accessible for full validation.

The Y and UV plane formats are correctly mapped per D3D11 specifications (R8_UNORM/R8G8_UNORM for NV12; R16_UNORM/R16G16_UNORM for P010). The dual-SRV approach—creating separate resource views of the same texture with different format interpretations—is the standard pattern for planar YUV video in Direct3D 11. Driver support for this approach is well-established across all compliant D3D11 implementations.

However, shader source code is not present in the repository, so verification that shader sampling code correctly uses these plane views cannot be completed. Ensure shader code reads Y values from slot 0 (single-channel) and UV values from slot 1 (dual-channel interleaved format).

Comment thread Common/DeviceResources.cpp
Comment thread Common/DeviceResources.cpp Outdated
Comment thread Common/DeviceResources.h
Comment thread Common/DirectXHelper.cpp Outdated
Comment thread Common/DirectXHelper.h Outdated
Comment thread Streaming/PacerRational.h Outdated
Comment thread Streaming/StatsRenderer.cpp Outdated
Comment thread Streaming/VideoRenderer.cpp
Comment thread Utils/FloatBuffer.h Outdated
Comment thread vcpkg.json
@andygrundman
andygrundman marked this pull request as draft October 29, 2025 18:31
@andygrundman

Copy link
Copy Markdown
Collaborator Author

OK Rabbit, I'll fix some of these if you stop writing VSYNC in all caps.

@andygrundman

Copy link
Copy Markdown
Collaborator Author

@TheElixZammuto What do you think about this one it found? Without fully understanding this input code, it does seem like KeyUp ought to be Enter?

			//Start to Enter
			if (isPressed(reading.Buttons, GamepadButtons::Menu) && !isPressed(previousReading[i].Buttons, GamepadButtons::Menu)) {
				moonlightClient->KeyDown((unsigned short)Windows::System::VirtualKey::Enter, 0);
			}
			else if (isPressed(previousReading[i].Buttons, GamepadButtons::Menu)) {
				moonlightClient->KeyUp((unsigned short)Windows::System::VirtualKey::Right, 0);
			}

moonlight-xbox-dx.vcxproj.filters: remove duplicate items
Remove unused Microsoft code in DirectXHelper
LogRenderer: more efficient warning message
merge with dxhelper
Streaming/PacerRational.h: cleanup intrinsic
VideoRenderer: include libavutil headers
HostSettingsPage: include cmath
PlotDesc.h: check size of kPlotDescs array
moonlight_xbox_dxMain Input: loop should run at 1000hz
MoonlightClient.cpp: use std::lround when constructing config.clientRefreshRateX100
State/Stats.cpp: minor type fixes
FFmpegDecoder: remove unused code
Set ffmpeg log level to AV_LOG_ERROR
Pacer: fix newline in log message
Fix minor CR nitsquashs
HostSettingsPage: fix HDR4KNote GridColumn
HostSettingsPage: handle empty fps list
MsToQpc: fix rounding
FQLog: fix counter
FFmpegDecoder: correctly unref hw_device_ctx
FFmpegDecoder: free pkt correctly
Pacer: fix m_Running to be atomic
Pacer: pass QpcFreq to rational
StatsRenderer: fix row2
FloatBuffer: return size_t instead of int
vcpkg: remove ffmpeg features: avdevice, x264
@andygrundman
andygrundman marked this pull request as ready for review October 31, 2025 19:56
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@TheElixZammuto

Copy link
Copy Markdown
Owner

Testing went well with 1080p 60fps on Xbox Series S

Other uses cases were tested by other people, so more than happy to merge!

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