Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Common/DeviceResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ void DX::DeviceResources::SetSwapChainPanel(SwapChainPanel^ panel)
m_compositionScaleX = panel->CompositionScaleX;
m_compositionScaleY = panel->CompositionScaleY;

Utils::Logf("SwapChain logical size: %.0fx%.0f @ composition scale %.1fx%.1f\n",
m_logicalSize.Width, m_logicalSize.Height, m_compositionScaleX, m_compositionScaleY);

CreateWindowSizeDependentResources();

ComPtr<ISwapChainPanelNative> panelNative;
Expand Down
55 changes: 35 additions & 20 deletions State/Stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,23 @@ void Stats::SubmitPresentPacing(double presentDisplayMs) {
}

// High-level render loop timings
void Stats::SubmitRenderStats(int64_t preWaitTimeUs, int64_t renderTimeUs, int64_t presentTimeUs) {
void Stats::SubmitRenderStats(int64_t preWaitTimeUs, int64_t renderTimeUs, int64_t presentTimeUs, bool hitDeadline) {
std::lock_guard<std::mutex> lock(m_mutex);
m_ActiveWndVideoStats.totalRenderTimeUs += renderTimeUs;
m_ActiveWndVideoStats.renderedFrames++;

if (hitDeadline) {
m_ActiveWndVideoStats.hitDeadlines++;
} else {
m_ActiveWndVideoStats.missedDeadlines++;

#if defined(_DEBUG)
Utils::Logf("missed deadline: preWait + render: %.2f + %.2f = %.2f ms\n",
(double)preWaitTimeUs / 1000.0, (double)renderTimeUs / 1000.0,
(double)(preWaitTimeUs + renderTimeUs) / 1000.0);
#endif
}

// Only shown in debug builds
m_ActiveWndVideoStats.totalPreWaitTimeUs += preWaitTimeUs;
m_ActiveWndVideoStats.totalPresentTimeUs += presentTimeUs;
Expand All @@ -152,6 +164,8 @@ void Stats::addVideoStats(DX::StepTimer const& timer, VIDEO_STATS& src, VIDEO_ST
dst.totalFrames += src.totalFrames;
dst.networkDroppedFrames += src.networkDroppedFrames;
dst.pacerDroppedFrames += src.pacerDroppedFrames;
dst.hitDeadlines += src.hitDeadlines;
dst.missedDeadlines += src.missedDeadlines;
dst.totalReassemblyTimeUs += src.totalReassemblyTimeUs;
dst.totalDecodeTime += src.totalDecodeTime;
dst.totalPacerTimeUs += src.totalPacerTimeUs;
Expand Down Expand Up @@ -373,23 +387,24 @@ void Stats::formatVideoStats(DX::StepTimer const& timer, VIDEO_STATS& stats, cha
offset += ret;
}

// #if defined(_DEBUG)
// // Developer-only stats that might be too confusing
// // If you add lines here, add more height pixels in StatsRenderer::CreateWindowSizeDependentResources()
// if (stats.renderedFrames != 0) {
// ret = snprintf(&output[offset],
// length - offset,
// "------\n"
// "PreWait/Render/Present: %.2f / %.2f / %.2f ms\n",
// (double)stats.totalPreWaitTimeUs / 1000.0 / stats.renderedFrames,
// (double)stats.totalRenderTimeUs / 1000.0 / stats.renderedFrames,
// (double)stats.totalPresentTimeUs / 1000.0 / stats.renderedFrames);
// if (ret < 0 || (size_t)ret >= (length - offset)) {
// Utils::Log("Error: stringifyVideoStats length overflow\n");
// return;
// }

// offset += ret;
// }
// #endif
#if defined(_DEBUG)
// Developer-only stats that might be too confusing
// If you add lines here, add more height pixels in StatsRenderer::CreateWindowSizeDependentResources()
if (stats.renderedFrames != 0) {
ret = snprintf(&output[offset],
length - offset,
"------\n"
"Missed present rate: %.2f%%\n"
"PreWait/Render: %.2f/%.2f ms\n",
stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,

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.

⚠️ Potential issue | 🟡 Minor

Logic issue in missed present rate calculation.

The condition stats.hitDeadlines ? ... : 0.0f will incorrectly show 0% if there are only missed deadlines (hitDeadlines=0, missedDeadlines>0). The check should verify the total is non-zero instead.

🐛 Proposed fix
-					   stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
+					   (stats.hitDeadlines + stats.missedDeadlines) ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stats.hitDeadlines ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
(stats.hitDeadlines + stats.missedDeadlines) ? ((double)stats.missedDeadlines / (stats.missedDeadlines + stats.hitDeadlines)) * 100 : 0.0f,
🤖 Prompt for AI Agents
In @State/Stats.cpp at line 399, The missed-present-rate calculation uses
`stats.hitDeadlines ? ... : 0.0f` which returns 0 when there are only missed
deadlines; change the conditional to check the total (`stats.missedDeadlines +
stats.hitDeadlines`) and compute `((double)stats.missedDeadlines /
(stats.missedDeadlines + stats.hitDeadlines)) * 100` when the total is non-zero,
otherwise return 0.0f, ensuring the numerator/denominator use a floating-point
cast to avoid integer division.

(double)stats.totalPreWaitTimeUs / 1000.0 / stats.renderedFrames,
(double)stats.totalRenderTimeUs / 1000.0 / stats.renderedFrames);
if (ret < 0 || (size_t)ret >= (length - offset)) {
Utils::Log("Error: stringifyVideoStats length overflow\n");
return;
}

offset += ret;
}
#endif
}
4 changes: 3 additions & 1 deletion State/Stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ typedef struct _VIDEO_STATS {
uint32_t totalFrames;
uint32_t networkDroppedFrames;
uint32_t pacerDroppedFrames;
uint32_t hitDeadlines;
uint32_t missedDeadlines;
uint16_t minHostProcessingLatency;
uint16_t maxHostProcessingLatency;
uint32_t totalHostProcessingLatency;
Expand Down Expand Up @@ -61,7 +63,7 @@ namespace moonlight_xbox_dx
void SubmitAvgQueueSize(float avgQueueSize);
void SubmitPacerTime(int64_t pacerTimeQpc);
void SubmitPresentPacing(double presentDisplayMs);
void SubmitRenderStats(int64_t preWaitTimeUs, int64_t renderTimeUs, int64_t presentTimeUs);
void SubmitRenderStats(int64_t preWaitTimeUs, int64_t renderTimeUs, int64_t presentTimeUs, bool hitDeadline);

private:
void addVideoStats(DX::StepTimer const& timer, VIDEO_STATS& src, VIDEO_STATS& dst);
Expand Down
23 changes: 17 additions & 6 deletions Streaming/FFmpegDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,16 @@ namespace moonlight_xbox_dx {
}

void FFMpegDecoder::CompleteInitialization(const std::shared_ptr<DX::DeviceResources>& res, STREAM_CONFIGURATION *config, bool framePacingImmediate) {
m_deviceResources = res;
this->m_deviceResources = res;
this->fps = config->fps;
Pacer::instance().init(res, config->fps, res->GetRefreshRate(), framePacingImmediate);
}

int FFMpegDecoder::Init(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
this->videoFormat = videoFormat;
this->width = width;
this->height = height;
this->fps = 60; // correctly set in CompleteInitialization

this->m_LastFrameNumber = 0;
this->ffmpeg_buffer_size = 0;
Expand Down Expand Up @@ -266,10 +268,7 @@ namespace moonlight_xbox_dx {
av_frame_free(&frame);
return DR_NEED_IDR;
}
// Not the best way to handle this. BUT IT DOES FIX XBOX ONE TEARING!!!!
// Honestly this did take too much time of my life (and AndyG life too) to care to make a better version
// If you want to fix this, have fun! (And hopefully you have Microsoft blessing/tools/support for that)
if (LiGetPendingVideoFrames() < 2 && IsXboxOne()) moonlight_xbox_dx::usleep(12000);

// Capture a frame timestamp to measuring pacing delay
QueryPerformanceCounter(&decodeEnd);
frame_attach_userdata(frame, decodeEnd.QuadPart);
Expand All @@ -287,10 +286,22 @@ namespace moonlight_xbox_dx {
// again where we expect to get AVERROR(EAGAIN) and break out.
}

double decodeTimeMs = QpcToMs(decodeEnd.QuadPart - decodeStart.QuadPart);
if (decodeEnd.QuadPart > decodeStart.QuadPart) {
m_deviceResources->GetStats()->SubmitDecodeMs(QpcToMs(decodeEnd.QuadPart - decodeStart.QuadPart));
m_deviceResources->GetStats()->SubmitDecodeMs(decodeTimeMs);
}

// Not the best way to handle this. BUT IT DOES FIX XBOX ONE TEARING!!!!
// Honestly this did take too much time of my life (and AndyG life too) to care to make a better version
// If you want to fix this, have fun! (And hopefully you have Microsoft blessing/tools/support for that)
// if (IsXboxOne()) {
// float remainingMs = (1000.0f / fps) - decodeTimeMs - 2.0; // 2ms buffer time
// if (remainingMs > 0.0) {
// //Utils::Logf("SubmitDecodeUnit sleeping %.3fms\n", remainingMs);
// SleepUntilQpc(QpcNow() + MsToQpc(remainingMs));
// }
// }

return DR_OK;
}

Expand Down
2 changes: 1 addition & 1 deletion Streaming/FFmpegDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class FFMpegDecoder {
int SubmitDecodeUnit(PDECODE_UNIT decodeUnit);
static FFMpegDecoder *getInstance();
static DECODER_RENDERER_CALLBACKS getDecoder();
int videoFormat, width, height;
int videoFormat, width, height, fps;
std::recursive_mutex m_mutex;

// locking helper
Expand Down
2 changes: 1 addition & 1 deletion Streaming/LogRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ void LogRenderer::CreateDeviceDependentResources()
}

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(), font);

// use much faster font rendering
m_console->SetFixedWidthFont(true);
Expand Down
12 changes: 9 additions & 3 deletions Streaming/Pacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ void Pacer::init(const std::shared_ptr<DX::DeviceResources> &res, int streamFps,

m_FrameCadence.init(m_RefreshRate > 0.0 ? m_RefreshRate : 60.0, static_cast<double>(streamFps));

Utils::Logf("Frame Pacer init: mode %s, streamFps %d, refreshRate %.2f\n",
m_FramePacingImmediate ? "immediate" : "display-locked", m_StreamFps, m_RefreshRate);

m_vhsum = 0;
m_vhcount = 0;
m_vhidx = 0;
Expand Down Expand Up @@ -326,9 +329,9 @@ bool Pacer::renderModeDisplayLocked(std::shared_ptr<VideoRenderer> &sceneRendere
return true; // ok to Present()
}

// called by render thread
void Pacer::waitBeforePresent(int64_t target) {
if (!running()) return;
// called by render thread, returns true if we waited, false if we missed the target
bool Pacer::waitBeforePresent(int64_t target) {
if (!running()) return false;

int64_t now = QpcNow();
if (target <= 0) {
Expand All @@ -340,7 +343,10 @@ void Pacer::waitBeforePresent(int64_t target) {
if (target > now) {
FQLog("waitBeforePresent(): waiting %.3fms\n", QpcToMs(target - now));
SleepUntilQpc(target);
return true;
}

return false;
}

// called by render thread
Expand Down
2 changes: 1 addition & 1 deletion Streaming/Pacer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Pacer {
void init(const std::shared_ptr<DX::DeviceResources> &res, int maxVideoFps, double refreshRate, bool framePacingImmediate);
void waitForFrame(double timeoutMs);
bool renderOnMainThread(std::shared_ptr<moonlight_xbox_dx::VideoRenderer> &sceneRenderer);
void waitBeforePresent(int64_t deadline);
bool waitBeforePresent(int64_t deadline);
int64_t getCurrentFramePts();
int64_t getNextVBlankQpc(int64_t *now);
void submitFrame(AVFrame *frame);
Expand Down
5 changes: 3 additions & 2 deletions Streaming/moonlight_xbox_dxMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ void moonlight_xbox_dxMain::StartRenderLoop()

// Whether we rendered a new frame or not, wait until vblank for pacing
// This is out of the lock and won't block the decoder
Pacer::instance().waitBeforePresent(deadline);
bool hitDeadline = Pacer::instance().waitBeforePresent(deadline);
t3 = QpcNow();

if (!rendered) {
Expand Down Expand Up @@ -173,7 +173,8 @@ void moonlight_xbox_dxMain::StartRenderLoop()
m_deviceResources->GetStats()->SubmitRenderStats(
QpcToUs(t1 - t0),
QpcToUs(t2 - t1),
QpcToUs(t3 - t2));
QpcToUs(t3 - t2),
hitDeadline);

FQLog("render loop %.3fms frametime %.3fms (PreWait %.3fms + Render %.3fms (avg %.3f) + Present %.3fms)\n",
QpcToMs(t3 - t0),
Expand Down