[plasma/krdp] src: VideoStream: base the in-flight window on round-trip time

Nate Graham <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 909f866e375234601870b5d1ac899566fa89085c by Nate Graham, on behalf of Shouvik Kar.
Committed on 06/08/2026 at 13:37.
Pushed by ngraham into branch 'master'.

VideoStream: base the in-flight window on round-trip time

hasInFlightCapacity() used a fixed limit of two outstanding frames, which
limits throughput on high-latency links because two unacknowledged frames may
not keep the pipe full. Size maxInFlight from the average round-trip time and
requested frame rate instead, clamped to a floor of 2 and a one-second latency
budget.

NetworkDetection now publishes its RTT snapshots via atomics so VideoStream can
read the values safely across threads when deriving the cached in-flight window.

M  +15   -10   src/NetworkDetection.cpp
M  +27   -3    src/VideoStream.cpp
M  +2    -0    src/VideoStream.h

https://invent.kde.org/plasma/krdp/-/commit/909f866e375234601870b5d1ac899566fa89085c

diff --git a/src/NetworkDetection.cpp b/src/NetworkDetection.cpp
index 1613762..6fd45f2 100644
--- a/src/NetworkDetection.cpp
+++ b/src/NetworkDetection.cpp
@@ -11,6 +11,8 @@
 
 #include <ranges>
 
+#include <atomic>
+
 #include <QQueue>
 #include <QTimer>
 
@@ -73,8 +75,9 @@ public:
     QHash<uint32_t, clk::system_clock::time_point> rttRequests;
     std::vector<RTTMeasurement> rttMeasurements;
 
-    clk::system_clock::duration minimumRtt;
-    clk::system_clock::duration averageRtt;
+    // Published as atomic tick counts so the getters are safe to read from any thread.
+    std::atomic<clk::system_clock::rep> minimumRttTicks{0};
+    std::atomic<clk::system_clock::rep> averageRttTicks{0};
 
     clk::system_clock::time_point lastNetworkResult;
 
@@ -93,12 +96,12 @@ NetworkDetection::~NetworkDetection() = default;
 
 std::chrono::system_clock::duration NetworkDetection::minimumRTT() const
 {
-    return d->minimumRtt;
+    return clk::system_clock::duration(d->minimumRttTicks.load());
 }
 
 std::chrono::system_clock::duration NetworkDetection::averageRTT() const
 {
-    return d->averageRtt;
+    return clk::system_clock::duration(d->averageRttTicks.load());
 }
 
 void NetworkDetection::initialize()
@@ -208,13 +211,15 @@ void NetworkDetection::updateAverageRtt()
         return;
     }
 
-    d->minimumRtt = std::numeric_limits<clk::system_clock::duration>::max();
+    auto minimum = std::numeric_limits<clk::system_clock::duration>::max();
     auto sum = clk::system_clock::duration(0);
-    std::for_each(d->rttMeasurements.begin(), d->rttMeasurements.end(), [this, &sum](const auto &measurement) {
-        d->minimumRtt = std::min(d->minimumRtt, measurement.roundTripTime);
+    std::for_each(d->rttMeasurements.begin(), d->rttMeasurements.end(), [&minimum, &sum](const auto &measurement) {
+        minimum = std::min(minimum, measurement.roundTripTime);
         sum = sum + measurement.roundTripTime;
     });
-    d->averageRtt = sum / d->rttMeasurements.size();
+    const auto average = sum / d->rttMeasurements.size();
+    d->minimumRttTicks.store(minimum.count());
+    d->averageRttTicks.store(average.count());
 
     Q_EMIT rttChanged();
 
@@ -230,8 +235,8 @@ void NetworkDetection::updateAverageRtt()
 
     rdpNetworkCharacteristicsResult result;
     result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
-    result.baseRTT = clk::duration_cast<clk::milliseconds>(d->minimumRtt).count();
-    result.averageRTT = clk::duration_cast<clk::milliseconds>(d->averageRtt).count();
+    result.baseRTT = clk::duration_cast<clk::milliseconds>(minimum).count();
+    result.averageRTT = clk::duration_cast<clk::milliseconds>(average).count();
     result.bandwidth = d->lastBandwithMeasurement;
     d->rdpAutodetect->NetworkCharacteristicsResult(d->rdpAutodetect, RDP_TRANSPORT_TCP, d->nextSequenceNumber(), &result);
 }
diff --git a/src/VideoStream.cpp b/src/VideoStream.cpp
index 0bdc35c..b22fc3f 100644
--- a/src/VideoStream.cpp
+++ b/src/VideoStream.cpp
@@ -10,6 +10,8 @@
 #include "VideoStream.h"
 
 #include <algorithm>
+#include <atomic>
+#include <cmath>
 #include <condition_variable>
 
 #include <QDateTime>
@@ -35,8 +37,9 @@ namespace KRdp
 
 namespace clk = std::chrono;
 
-// Maximum number of frames to contain in the queue.
-constexpr qsizetype MaximumInFlightFrames = 2;
+constexpr qsizetype MaximumInFlightFrames = 2; // in-flight window floor
+constexpr double InFlightGain = 1.0; // window spans this many round trips of frames
+constexpr double LatencyBudgetSec = 1.0; // never buffer more than this many seconds of video
 constexpr uint32_t ProgressiveCodecContextId = 1;
 struct RdpCapsInformation {
     uint32_t version;
@@ -146,6 +149,7 @@ public:
     std::mutex pendingFramesMutex;
 
     std::atomic_int requestedFrameRate = 60;
+    std::atomic<qsizetype> maxInFlight{MaximumInFlightFrames}; // recomputed from RTT on rttChanged
     bool initialized = false;
     quint8 quality = 100;
 
@@ -352,6 +356,8 @@ bool VideoStream::initialize()
 
     d->initialized = true;
 
+    connect(d->session->networkDetection(), &NetworkDetection::rttChanged, this, &VideoStream::updateInFlightWindow);
+
     d->frameSubmissionThread = std::jthread([this](std::stop_token token) {
         while (!token.stop_requested()) {
             if (!hasInFlightCapacity() || !d->gfxContext || !d->capsConfirmed) {
@@ -822,10 +828,28 @@ void VideoStream::performReset(QSize size)
     }
 }
 
+void VideoStream::updateInFlightWindow()
+{
+    // Size the in-flight window from the bandwidth-delay product: at the current source
+    // rate, how many frames fit within one base round trip. NetworkDetection publishes the
+    // RTT via atomics, so averageRTT() is safe to read here; hasInFlightCapacity() then
+    // just reads the cached maxInFlight. Falls back to the fixed floor until an RTT is known.
+    const auto rttMs = clk::duration_cast<clk::milliseconds>(d->session->networkDetection()->averageRTT()).count();
+    qsizetype window = MaximumInFlightFrames;
+    if (rttMs > 0 && rttMs < 60000) {
+        const double rttSec = rttMs / 1000.0;
+        const int fps = d->requestedFrameRate.load();
+        const qsizetype bdp = qsizetype(std::ceil(fps * rttSec * InFlightGain));
+        const qsizetype cap = std::max<qsizetype>(MaximumInFlightFrames, qsizetype(std::ceil(fps * LatencyBudgetSec)));
+        window = std::clamp(bdp, qsizetype(MaximumInFlightFrames), cap);
+    }
+    d->maxInFlight.store(window);
+}
+
 bool VideoStream::hasInFlightCapacity() const
 {
     std::lock_guard lock(d->pendingFramesMutex);
-    return d->pendingFrames.size() < MaximumInFlightFrames;
+    return d->pendingFrames.size() < d->maxInFlight.load();
 }
 
 void VideoStream::sendFrame(const VideoFrame &frame)
diff --git a/src/VideoStream.h b/src/VideoStream.h
index a84738f..285029b 100644
--- a/src/VideoStream.h
+++ b/src/VideoStream.h
@@ -108,6 +108,8 @@ private:
     void sendFrameH264(const VideoFrame &frame);
     void sendFrameProgressive(const VideoFrame &frame);
 
+    void updateInFlightWindow();
+
     class Private;
     const std::unique_ptr<Private> d;
 };
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.