Skip to content

Commit 855c76b

Browse files
committed
Fix Xbox One performance regressions. Refactor render loop wait behavior and re-enable explicit ffmpeg/D3D locking.
1 parent 3137727 commit 855c76b

9 files changed

Lines changed: 195 additions & 132 deletions

File tree

State/Stats.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ void Stats::formatVideoStats(DX::StepTimer const& timer, VIDEO_STATS& stats, cha
276276
if (stats.receivedFps > 0) {
277277
ret = snprintf(&output[offset],
278278
length - offset,
279-
"Video stream: %dx%d %.2f FPS (Codec: %s)\n",
279+
"Video stream: %dx%d %.2f FPS (%s)\n",
280280
ffmpeg.width,
281281
ffmpeg.height,
282282
stats.totalFps,
@@ -355,7 +355,7 @@ void Stats::formatVideoStats(DX::StepTimer const& timer, VIDEO_STATS& stats, cha
355355
"Average network latency: %s\n"
356356
"Average reassembly/decoding time: %.2f/%.2f ms\n"
357357
"Average frames in queue: %.1f\n"
358-
"Average frame queue/render/present time: %.2f/%.2f/%.2f ms\n",
358+
"Average frame queue/render/present: %.2f/%.2f/%.2f ms\n",
359359
stats.totalFrames ? (double)stats.networkDroppedFrames / stats.totalFrames * 100 : 0.0f,
360360
stats.totalFrames ? (double)stats.pacerDroppedFrames / stats.totalFrames * 100 : 0.0f,
361361
rttString,

Streaming/FFmpegDecoder.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ namespace moonlight_xbox_dx {
5858
m_LastFrameNumber(0) {
5959
}
6060

61+
void lock_context(void *user) {
62+
auto me = (FFMpegDecoder*)user;
63+
me->m_mutex.lock();
64+
}
65+
66+
void unlock_context(void *user) {
67+
auto me = (FFMpegDecoder*)user;
68+
me->m_mutex.unlock();
69+
}
70+
6171
void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) {
6272
char lineBuffer[1024];
6373
static int printPrefix = 1;
@@ -75,7 +85,7 @@ namespace moonlight_xbox_dx {
7585
Utils::Logf(shouldPrefixThisMessage ? "[ffmpeg] %s" : "%s", lineBuffer);
7686
}
7787

78-
void FFMpegDecoder::CompleteInitialization(const std::shared_ptr<DX::DeviceResources>& res, STREAM_CONFIGURATION *config) {
88+
void FFMpegDecoder::CompleteInitialization(const std::shared_ptr<DX::DeviceResources>& res, STREAM_CONFIGURATION *config) {
7989
m_deviceResources = res;
8090
Pacer::instance().init(res, config->fps, res->GetRefreshRate());
8191
}
@@ -124,6 +134,9 @@ namespace moonlight_xbox_dx {
124134
d3d11va_device_ctx = reinterpret_cast<AVD3D11VADeviceContext*>(device_ctx->hwctx);
125135
d3d11va_device_ctx->device = m_deviceResources->GetD3DDevice();
126136
d3d11va_device_ctx->device_context = m_deviceResources->GetD3DDeviceContext();
137+
d3d11va_device_ctx->lock = lock_context;
138+
d3d11va_device_ctx->unlock = unlock_context;
139+
d3d11va_device_ctx->lock_ctx = this;
127140
int err2;
128141
if ((err2 = av_hwdevice_ctx_init(hw_device_ctx)) < 0) {
129142
Utils::Logf("Failed to create specified DirectX Video device: %d\n", err2);
@@ -139,7 +152,7 @@ namespace moonlight_xbox_dx {
139152
decoder_ctx->width = width;
140153
decoder_ctx->height = height;
141154

142-
int err = avcodec_open2(decoder_ctx, decoder, NULL);
155+
int err = avcodec_open2(decoder_ctx, decoder, NULL);
143156
if (err < 0) {
144157
char msg[2048];
145158
sprintf(msg, "Failed to create FFMpeg Codec: %d\n", err);
@@ -157,13 +170,6 @@ namespace moonlight_xbox_dx {
157170
return -1;
158171
}
159172

160-
// Put D3D11 in multithread-friendly mode
161-
ID3D11Multithread *pMultithread = nullptr;
162-
HRESULT hr = d3d11va_device_ctx->device->QueryInterface(__uuidof(ID3D11Multithread), (void **)&pMultithread);
163-
if (SUCCEEDED(hr)) {
164-
pMultithread->SetMultithreadProtected(TRUE);
165-
pMultithread->Release();
166-
}
167173
return 0;
168174
}
169175

Streaming/FFmpegDecoder.h

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <atomic>
4+
#include <mutex>
45
#include <queue>
56
#include "../Common/StepTimer.h"
67
#include "Pacer.h"
@@ -17,9 +18,9 @@ extern "C" {
1718
#define MAX_BUFFER 1024 * 1024
1819

1920
typedef struct MLFrameData {
20-
int64_t decodeEndQpc; // when we finished decoding
21-
int64_t presentTargetQpc; // timestamp when frame should be presented (slightly earlier than vsync)
22-
int64_t presentVsyncQpc; // hard vsync deadline
21+
int64_t decodeEndQpc; // when we finished decoding
22+
int64_t presentTargetQpc; // timestamp when frame should be presented (slightly earlier than vsync)
23+
int64_t presentVsyncQpc; // hard vsync deadline
2324
} MLFrameData;
2425

2526
namespace moonlight_xbox_dx {
@@ -36,6 +37,28 @@ class FFMpegDecoder {
3637
static FFMpegDecoder *getInstance();
3738
static DECODER_RENDERER_CALLBACKS getDecoder();
3839
int videoFormat, width, height;
40+
std::recursive_mutex m_mutex;
41+
42+
// locking helper
43+
class LockGuard {
44+
public:
45+
explicit LockGuard(FFMpegDecoder &ff)
46+
: m_ff(ff) {
47+
m_ff.m_mutex.lock();
48+
}
49+
~LockGuard() {
50+
m_ff.m_mutex.unlock();
51+
}
52+
LockGuard(const LockGuard &) = delete;
53+
LockGuard &operator=(const LockGuard &) = delete;
54+
55+
private:
56+
FFMpegDecoder &m_ff;
57+
};
58+
59+
[[nodiscard]] static LockGuard Lock() {
60+
return LockGuard(instance());
61+
}
3962

4063
private:
4164
FFMpegDecoder();

Streaming/Pacer.cpp

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,21 @@ void Pacer::updateFrameStats() {
190190

191191
// Main render thread
192192

193-
void Pacer::waitForFrame(double timeoutMs) {
193+
void Pacer::waitForFrame(double timeoutMs, bool waitFullTime) {
194194
if (!running()) return;
195195

196-
// mark frame begin for use by afterPresent
197-
m_BeginFrameQpc = QpcNow();
198-
199196
// Wait for a decoded frame to be available
197+
int64_t t0 = QpcNow();
200198
int queueHas = 1;
201199
FrameQueue::instance().waitForEnqueue(queueHas, timeoutMs);
200+
201+
if (waitFullTime) {
202+
int64_t t1 = QpcNow();
203+
timeoutMs -= QpcToMs(t1 - t0);
204+
if (timeoutMs > 0.0) {
205+
SleepUntilQpc(t1 + MsToQpc(timeoutMs));
206+
}
207+
}
202208
}
203209

204210
// called by render thread
@@ -214,7 +220,9 @@ bool Pacer::renderOnMainThread(std::shared_ptr<VideoRenderer> &sceneRenderer) {
214220

215221
// Render it
216222
FQLog("> Frame rendered [pts: %.3f]\n", frame->pts / 90.0);
217-
sceneRenderer->Render(frame);
223+
if (!sceneRenderer->Render(frame)) {
224+
return false; // something went wrong rendering the frame
225+
}
218226

219227
if (frame->opaque_ref) {
220228
// Count time spent in FrameQueue
@@ -227,26 +235,23 @@ bool Pacer::renderOnMainThread(std::shared_ptr<VideoRenderer> &sceneRenderer) {
227235
}
228236

229237
// called by render thread
230-
void Pacer::waitBeforePresent() {
238+
void Pacer::waitBeforePresent(int64_t deadline = 0) {
231239
if (!running()) return;
232240

233241
// Wait until vsync
234-
int64_t now, interval = 0;
235-
int64_t target = getNextVBlankQpc(&now, &interval);
236-
237-
if (IsXbox() && m_StreamFps == 120 && m_RefreshRate > 119.0) {
238-
// 120hz on Xbox requires us to present each frame at half-vsync intervals
239-
int64_t half = interval / 2;
240-
if (target - half > now) {
241-
// we're currently in the first half of a vblank, sleep till the halfway mark
242-
target -= half;
243-
}
242+
int64_t now = 0;
243+
int64_t target = getNextVBlankQpc(&now);
244+
if (deadline && deadline != target) {
245+
// we missed the deadline
246+
target = deadline;
244247
}
245248

246249
m_LastSyncTarget.store(target, std::memory_order_release);
247250

248-
FQLog("waitBeforePresent(): waiting %.3fms to target %lld\n", QpcToMs(target - now), target);
249-
SleepUntilQpc(target);
251+
if (target > now) {
252+
FQLog("waitBeforePresent(): waiting %.3fms\n", QpcToMs(target - now));
253+
SleepUntilQpc(target);
254+
}
250255
}
251256

252257
// end main thread
@@ -267,23 +272,34 @@ void Pacer::submitFrame(AVFrame *frame) {
267272

268273
// Caller often needs now and the vsync interval, since this needs locking
269274
// the logic is confined to this function.
270-
int64_t Pacer::getNextVBlankQpc(int64_t *now, int64_t *interval) {
275+
int64_t Pacer::getNextVBlankQpc(int64_t *now) {
271276
std::scoped_lock<std::mutex> lock(m_FrameStatsLock);
277+
int64_t target, interval = 0;
278+
*now = QpcNow();
272279

273280
if (m_LastSyncQpc == 0 || m_VsyncIntervalQpc == 0) {
274281
// Fallback until vsyncHardware spins up
275-
*now = QpcNow();
276282
double rr = m_RefreshRate > 0.0 ? m_RefreshRate : 60.0;
277-
*interval = MsToQpc(1000.0 / rr);
278-
return *now + *interval;
279-
}
283+
interval = MsToQpc(1000.0 / rr);
284+
target = *now + interval;
285+
} else {
286+
interval = m_VsyncIntervalQpc;
287+
int64_t next = m_LastSyncQpc;
280288

281-
*now = QpcNow();
282-
*interval = m_VsyncIntervalQpc;
283-
int64_t next = m_LastSyncQpc;
289+
while (next < *now) {
290+
next += interval;
291+
}
292+
target = next + static_cast<int64_t>(m_ewmaVsyncDriftQpc);
293+
}
284294

285-
while (next < *now) {
286-
next += *interval;
295+
if (IsXbox() && m_StreamFps == 120 && m_RefreshRate > 119.0) {
296+
// 120hz on Xbox requires us to present each frame at half-vsync intervals
297+
int64_t half = interval / 2;
298+
if (target - half > *now) {
299+
// we're currently in the first half of a vblank, sleep till the halfway mark
300+
target -= half;
301+
}
287302
}
288-
return next + static_cast<int64_t>(m_ewmaVsyncDriftQpc);
303+
304+
return target;
289305
}

Streaming/Pacer.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ class Pacer {
1919

2020
void deinit();
2121
void init(const std::shared_ptr<DX::DeviceResources> &res, int maxVideoFps, double refreshRate);
22-
void waitForFrame(double timeoutMs);
22+
void waitForFrame(double timeoutMs, bool waitFullTime);
2323
bool renderOnMainThread(std::shared_ptr<moonlight_xbox_dx::VideoRenderer> &sceneRenderer);
24-
void waitBeforePresent();
24+
void waitBeforePresent(int64_t deadline);
25+
int64_t getNextVBlankQpc(int64_t *now);
2526
void submitFrame(AVFrame *frame);
2627

2728
private:
@@ -39,15 +40,13 @@ class Pacer {
3940

4041
void vsyncHardware();
4142
void updateFrameStats();
42-
int64_t getNextVBlankQpc(int64_t *now, int64_t *interval);
4343

4444
std::shared_ptr<DX::DeviceResources> m_DeviceResources;
4545
std::thread m_VsyncThread;
4646
std::atomic<bool> m_Running{false};
4747
std::atomic<bool> m_Stopping{false};
4848
int m_StreamFps;
4949
double m_RefreshRate;
50-
int64_t m_BeginFrameQpc = 0;
5150

5251
static constexpr int VSYNC_HISTORY_SIZE = 512;
5352
std::mutex m_FrameStatsLock;

Streaming/VideoRenderer.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,34 @@ bool VideoRenderer::Render(AVFrame *frame) {
100100
auto *ctx = m_deviceResources->GetD3DDeviceContext();
101101
auto *dev = m_deviceResources->GetD3DDevice();
102102

103+
// Clear the back buffer
104+
ID3D11RenderTargetView* renderTarget[] = { m_deviceResources->GetBackBufferRenderTargetView() };
105+
ctx->ClearRenderTargetView(renderTarget[0], Colors::Black);
106+
107+
// Bind the back buffer. This needs to be done each time,
108+
// because the render target view will be unbound by Present().
109+
ctx->OMSetRenderTargets(1, renderTarget, nullptr);
110+
103111
ID3D11Texture2D *ffmpegTexture = (ID3D11Texture2D *)(frame->data[0]);
112+
if (!ffmpegTexture) {
113+
// This sometimes happens when reconnecting
114+
return false;
115+
}
104116
D3D11_TEXTURE2D_DESC ffmpegDesc;
105117
ffmpegTexture->GetDesc(&ffmpegDesc);
106118

107119
bool hasChanged = hasFrameFormatChanged(frame);
108120
if (hasChanged) {
109-
// Create our internal texture to copy and render
110121
setupVideoTexture(ffmpegDesc);
111122
}
112123

124+
// SRV 0 is always mapped to the video texture
125+
UINT srvIndex = 0;
113126
// Copy this frame into our video texture
114127
ctx->CopySubresourceRegion1(m_VideoTexture.Get(), 0, 0, 0, 0,
115-
(ID3D11Resource*)frame->data[0], (int)(intptr_t)frame->data[1],
128+
(ID3D11Resource *)frame->data[0], (int)(intptr_t)frame->data[1],
116129
nullptr, D3D11_COPY_DISCARD);
117130

118-
// SRV 0 is always mapped to the video texture
119-
UINT srvIndex = 0;
120-
121131
// Setup shader
122132
ctx->PSSetSamplers(0, 1, m_samplerState.GetAddressOf());
123133
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

0 commit comments

Comments
 (0)