180 lines
7.0 KiB
Diff
180 lines
7.0 KiB
Diff
--- a/app/streaming/session.cpp
|
|
+++ b/app/streaming/session.cpp
|
|
@@ -39,6 +39,7 @@
|
|
#include <QImage>
|
|
#include <QGuiApplication>
|
|
#include <QCursor>
|
|
+#include "input/vdi_input_priority.h"
|
|
#include <QScreen>
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
@@ -1962,8 +1963,14 @@
|
|
|
|
// Hijack this thread to be the SDL main thread. We have to do this
|
|
// because we want to suspend all Qt processing until the stream is over.
|
|
+ VdiInputPriority vdiInputPriority(qEnvironmentVariableIntValue("VDI_INPUT_PRIORITY") == 1,
|
|
+ SDL_CODE_FRAME_READY);
|
|
+ VdiInputTiming vdiInputTiming(qEnvironmentVariableIntValue("VDI_INPUT_TIMING") == 1);
|
|
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "VDI input priority: %s",
|
|
+ qEnvironmentVariableIntValue("VDI_INPUT_PRIORITY") == 1 ? "enabled" : "disabled");
|
|
SDL_Event event;
|
|
for (;;) {
|
|
+ if (!vdiInputPriority.takeDeferred(event, SDL_GetWindowID(m_Window))) {
|
|
#if SDL_VERSION_ATLEAST(2, 0, 18) && !defined(STEAM_LINK)
|
|
// SDL 2.0.18 has a proper wait event implementation that uses platform
|
|
// support to block on events rather than polling on Windows, macOS, X11,
|
|
@@ -1995,6 +2002,11 @@
|
|
continue;
|
|
}
|
|
#endif
|
|
+ if (vdiInputPriority.deferFrame(event)) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ vdiInputTiming.observe(event, SDL_GetWindowID(m_Window));
|
|
switch (event.type) {
|
|
case SDL_QUIT:
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
@@ -2005,7 +2017,9 @@
|
|
switch (event.user.code) {
|
|
case SDL_CODE_FRAME_READY:
|
|
if (m_VideoDecoder != nullptr) {
|
|
+ const auto renderStart = vdiInputTiming.startRender();
|
|
m_VideoDecoder->renderFrameOnMainThread();
|
|
+ vdiInputTiming.endRender(renderStart);
|
|
}
|
|
break;
|
|
case SDL_CODE_FLUSH_WINDOW_EVENT_BARRIER:
|
|
@@ -2308,6 +2322,7 @@
|
|
}
|
|
|
|
DispatchDeferredCleanup:
|
|
+ vdiInputTiming.report(vdiInputPriority.prioritized());
|
|
// Switch back to synchronous logging mode
|
|
StreamUtils::exitAsyncLoggingMode();
|
|
|
|
--- /dev/null
|
|
+++ b/app/streaming/input/vdi_input_priority.h
|
|
@@ -0,0 +1,121 @@
|
|
+#pragma once
|
|
+#include <SDL.h>
|
|
+#include <array>
|
|
+#include <algorithm>
|
|
+#include <cstdint>
|
|
+
|
|
+// SDL main thread only. Inputs may pass one frame-ready notification, never a
|
|
+// window/focus/quit/other notification or another frame. Input FIFO is preserved.
|
|
+class VdiInputPriority {
|
|
+public:
|
|
+ VdiInputPriority(bool enabled, Sint32 frameCode)
|
|
+ : m_Enabled(enabled), m_FrameCode(frameCode) {}
|
|
+
|
|
+ bool deferFrame(const SDL_Event& event) {
|
|
+ if (!m_Enabled || event.type != SDL_USEREVENT || event.user.code != m_FrameCode)
|
|
+ return false;
|
|
+ SDL_assert(!m_Pending);
|
|
+ m_Frame = event;
|
|
+ m_Pending = true;
|
|
+ m_Count = 0;
|
|
+ m_Start = SDL_GetPerformanceCounter();
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ bool takeDeferred(SDL_Event& event, Uint32 windowId) {
|
|
+ if (!m_Pending) return false;
|
|
+ SDL_Event next;
|
|
+ const auto elapsed = SDL_GetPerformanceCounter() - m_Start;
|
|
+ if (m_Count < 32 && elapsed < SDL_GetPerformanceFrequency() / 500 &&
|
|
+ SDL_PeepEvents(&next, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1 &&
|
|
+ isInputForWindow(next, windowId) &&
|
|
+ SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1) {
|
|
+ ++m_Count;
|
|
+ ++m_Prioritized;
|
|
+ return true;
|
|
+ }
|
|
+ event = m_Frame;
|
|
+ m_Pending = false;
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ uint64_t prioritized() const { return m_Prioritized; }
|
|
+
|
|
+ static bool isInputForWindow(const SDL_Event& event, Uint32 windowId) {
|
|
+ switch (event.type) {
|
|
+ case SDL_MOUSEMOTION:
|
|
+ return event.motion.windowID == windowId && event.motion.which != SDL_TOUCH_MOUSEID;
|
|
+ case SDL_MOUSEBUTTONDOWN:
|
|
+ case SDL_MOUSEBUTTONUP:
|
|
+ return event.button.windowID == windowId && event.button.which != SDL_TOUCH_MOUSEID;
|
|
+ case SDL_MOUSEWHEEL:
|
|
+ return event.wheel.windowID == windowId && event.wheel.which != SDL_TOUCH_MOUSEID;
|
|
+ case SDL_KEYDOWN:
|
|
+ case SDL_KEYUP:
|
|
+ return event.key.windowID == windowId;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+private:
|
|
+ bool m_Enabled;
|
|
+ Sint32 m_FrameCode;
|
|
+ bool m_Pending = false;
|
|
+ SDL_Event m_Frame{};
|
|
+ unsigned m_Count = 0;
|
|
+ Uint64 m_Start = 0;
|
|
+ uint64_t m_Prioritized = 0;
|
|
+};
|
|
+
|
|
+// Aggregated only when explicitly requested. Ages are SDL queue/dispatch ages,
|
|
+// not network RTT or physical input-to-photon latency. 0.1 ms histogram bins.
|
|
+class VdiInputTiming {
|
|
+public:
|
|
+ explicit VdiInputTiming(bool enabled) : m_Enabled(enabled) {}
|
|
+ void observe(const SDL_Event& event, Uint32 windowId) {
|
|
+ if (!m_Enabled || !event.common.timestamp ||
|
|
+ !VdiInputPriority::isInputForWindow(event, windowId)) return;
|
|
+ const Uint32 age = SDL_GetTicks() - event.common.timestamp;
|
|
+ // Ignore invalid/future synthetic timestamps; subtraction handles wrap.
|
|
+ if (age < 60000) m_Input.add(Uint64(age) * 10);
|
|
+ }
|
|
+ Uint64 startRender() const { return m_Enabled ? SDL_GetPerformanceCounter() : 0; }
|
|
+ void endRender(Uint64 start) {
|
|
+ if (m_Enabled) m_Render.add((SDL_GetPerformanceCounter() - start) * 10000 / SDL_GetPerformanceFrequency());
|
|
+ }
|
|
+ void report(uint64_t prioritized) const {
|
|
+ if (!m_Enabled) return;
|
|
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
+ "VDI input timing: SDL dispatch age only; prioritized=%llu",
|
|
+ (unsigned long long)prioritized);
|
|
+ m_Input.report("input dispatch age");
|
|
+ m_Render.report("main-thread render call");
|
|
+ }
|
|
+private:
|
|
+ struct Histogram {
|
|
+ std::array<uint64_t, 1024> bins{};
|
|
+ uint64_t count = 0, total = 0, max = 0;
|
|
+ void add(uint64_t tenths) {
|
|
+ ++bins[std::min<uint64_t>(tenths, bins.size()-1)];
|
|
+ ++count; total += tenths; max = std::max(max, tenths);
|
|
+ }
|
|
+ double quantile(unsigned percent) const {
|
|
+ const uint64_t rank = (count * percent + 99) / 100;
|
|
+ uint64_t accumulated = 0;
|
|
+ for (size_t i = 0; i < bins.size(); ++i) {
|
|
+ accumulated += bins[i];
|
|
+ if (accumulated >= rank) return i / 10.0;
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+ void report(const char* label) const {
|
|
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
+ "VDI %s: samples=%llu avg=%.2f p50=%.1f p95=%.1f max=%.1f ms (percentiles capped at 102.3ms)",
|
|
+ label, (unsigned long long)count, count ? total / (10.0*count) : 0,
|
|
+ count ? quantile(50) : 0, count ? quantile(95) : 0, max / 10.0);
|
|
+ }
|
|
+ };
|
|
+ bool m_Enabled;
|
|
+ Histogram m_Input, m_Render;
|
|
+};
|