Skip to content

Commit d9c0fc9

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

10 files changed

Lines changed: 189 additions & 132 deletions

File tree

Common/DeviceResources.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,6 @@ void DX::DeviceResources::Present()
524524
{
525525
HRESULT hr = m_swapChain->Present(0, 0);
526526

527-
// Discard the contents of the render target.
528-
// This is a valid operation only when the existing contents will be entirely
529-
// overwritten. If dirty or scroll rects are used, this call should be modified.
530-
m_d3dContext->DiscardView1(m_d3dRenderTargetView.Get(), nullptr, 0);
531-
532527
// If the device was removed either by a disconnection or a driver upgrade, we
533528
// must recreate all device resources.
534529
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)

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: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Pacer::Pacer()
4646
m_Stopping(false),
4747
m_StreamFps(0),
4848
m_RefreshRate(0.0),
49+
m_LastSyncRefreshCount(0),
4950
m_LastSyncQpc(0),
5051
m_VsyncIntervalQpc(0) {
5152
}
@@ -193,10 +194,8 @@ void Pacer::updateFrameStats() {
193194
void Pacer::waitForFrame(double timeoutMs) {
194195
if (!running()) return;
195196

196-
// mark frame begin for use by afterPresent
197-
m_BeginFrameQpc = QpcNow();
198-
199197
// Wait for a decoded frame to be available
198+
int64_t t0 = QpcNow();
200199
int queueHas = 1;
201200
FrameQueue::instance().waitForEnqueue(queueHas, timeoutMs);
202201
}
@@ -214,7 +213,9 @@ bool Pacer::renderOnMainThread(std::shared_ptr<VideoRenderer> &sceneRenderer) {
214213

215214
// Render it
216215
FQLog("> Frame rendered [pts: %.3f]\n", frame->pts / 90.0);
217-
sceneRenderer->Render(frame);
216+
if (!sceneRenderer->Render(frame)) {
217+
return false; // something went wrong rendering the frame
218+
}
218219

219220
if (frame->opaque_ref) {
220221
// Count time spent in FrameQueue
@@ -227,26 +228,23 @@ bool Pacer::renderOnMainThread(std::shared_ptr<VideoRenderer> &sceneRenderer) {
227228
}
228229

229230
// called by render thread
230-
void Pacer::waitBeforePresent() {
231+
void Pacer::waitBeforePresent(int64_t deadline) {
231232
if (!running()) return;
232233

233234
// 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-
}
235+
int64_t now = 0;
236+
int64_t target = getNextVBlankQpc(&now);
237+
if (deadline && deadline != target) {
238+
// we missed the deadline
239+
target = deadline;
244240
}
245241

246242
m_LastSyncTarget.store(target, std::memory_order_release);
247243

248-
FQLog("waitBeforePresent(): waiting %.3fms to target %lld\n", QpcToMs(target - now), target);
249-
SleepUntilQpc(target);
244+
if (target > now) {
245+
FQLog("waitBeforePresent(): waiting %.3fms\n", QpcToMs(target - now));
246+
SleepUntilQpc(target);
247+
}
250248
}
251249

252250
// end main thread
@@ -267,23 +265,34 @@ void Pacer::submitFrame(AVFrame *frame) {
267265

268266
// Caller often needs now and the vsync interval, since this needs locking
269267
// the logic is confined to this function.
270-
int64_t Pacer::getNextVBlankQpc(int64_t *now, int64_t *interval) {
268+
int64_t Pacer::getNextVBlankQpc(int64_t *now) {
271269
std::scoped_lock<std::mutex> lock(m_FrameStatsLock);
270+
int64_t target, interval = 0;
271+
*now = QpcNow();
272272

273273
if (m_LastSyncQpc == 0 || m_VsyncIntervalQpc == 0) {
274274
// Fallback until vsyncHardware spins up
275-
*now = QpcNow();
276275
double rr = m_RefreshRate > 0.0 ? m_RefreshRate : 60.0;
277-
*interval = MsToQpc(1000.0 / rr);
278-
return *now + *interval;
279-
}
276+
interval = MsToQpc(1000.0 / rr);
277+
target = *now + interval;
278+
} else {
279+
interval = m_VsyncIntervalQpc;
280+
int64_t next = m_LastSyncQpc;
280281

281-
*now = QpcNow();
282-
*interval = m_VsyncIntervalQpc;
283-
int64_t next = m_LastSyncQpc;
282+
while (next < *now) {
283+
next += interval;
284+
}
285+
target = next + static_cast<int64_t>(m_ewmaVsyncDriftQpc);
286+
}
284287

285-
while (next < *now) {
286-
next += *interval;
288+
if (IsXbox() && m_StreamFps == 120 && m_RefreshRate > 119.0) {
289+
// 120hz on Xbox requires us to present each frame at half-vsync intervals
290+
int64_t half = interval / 2;
291+
if (target - half > *now) {
292+
// we're currently in the first half of a vblank, sleep till the halfway mark
293+
target -= half;
294+
}
287295
}
288-
return next + static_cast<int64_t>(m_ewmaVsyncDriftQpc);
296+
297+
return target;
289298
}

Streaming/Pacer.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class Pacer {
2121
void init(const std::shared_ptr<DX::DeviceResources> &res, int maxVideoFps, double refreshRate);
2222
void waitForFrame(double timeoutMs);
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)