summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--video/out/d3d11/context.c124
1 files changed, 124 insertions, 0 deletions
diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c
index 3b0ab49812..fd867c243e 100644
--- a/video/out/d3d11/context.c
+++ b/video/out/d3d11/context.c
@@ -17,6 +17,7 @@
#include "common/msg.h"
#include "options/m_config.h"
+#include "osdep/timer.h"
#include "osdep/windows_utils.h"
#include "video/out/gpu/context.h"
@@ -68,6 +69,12 @@ struct priv {
struct ra_tex *backbuffer;
ID3D11Device *device;
IDXGISwapChain *swapchain;
+
+ int64_t perf_freq;
+ unsigned last_sync_refresh_count;
+ int64_t last_sync_qpc_time;
+ int64_t vsync_duration_qpc;
+ int64_t last_submit_qpc;
};
static struct ra_tex *get_backbuffer(struct ra_ctx *ctx)
@@ -141,15 +148,127 @@ static bool d3d11_submit_frame(struct ra_swapchain *sw,
return true;
}
+static int64_t qpc_to_us(struct ra_swapchain *sw, int64_t qpc)
+{
+ struct priv *p = sw->priv;
+
+ // Convert QPC units (1/perf_freq seconds) to microseconds. This will work
+ // without overflow because the QPC value is guaranteed not to roll-over
+ // within 100 years, so perf_freq must be less than 2.9*10^9.
+ return qpc / p->perf_freq * 1000000 +
+ qpc % p->perf_freq * 1000000 / p->perf_freq;
+}
+
+static int64_t qpc_us_now(struct ra_swapchain *sw)
+{
+ LARGE_INTEGER perf_count;
+ QueryPerformanceCounter(&perf_count);
+ return qpc_to_us(sw, perf_count.QuadPart);
+}
+
static void d3d11_swap_buffers(struct ra_swapchain *sw)
{
struct priv *p = sw->priv;
+
+ LARGE_INTEGER perf_count;
+ QueryPerformanceCounter(&perf_count);
+ p->last_submit_qpc = perf_count.QuadPart;
+
IDXGISwapChain_Present(p->swapchain, p->opts->sync_interval, 0);
}
+static void d3d11_get_vsync(struct ra_swapchain *sw, struct vo_vsync_info *info)
+{
+ struct priv *p = sw->priv;
+ HRESULT hr;
+
+ // The calculations below are only valid if mpv presents on every vsync
+ if (p->opts->sync_interval != 1)
+ return;
+
+ // GetLastPresentCount returns a sequential ID for the frame submitted by
+ // the last call to IDXGISwapChain::Present()
+ UINT submit_count;
+ hr = IDXGISwapChain_GetLastPresentCount(p->swapchain, &submit_count);
+ if (FAILED(hr))
+ return;
+
+ // GetFrameStatistics returns two pairs. The first is (PresentCount,
+ // PresentRefreshCount) which relates a present ID (on the same timeline as
+ // GetLastPresentCount) to the physical vsync it was displayed on. The
+ // second is (SyncRefreshCount, SyncQPCTime), which relates a physical vsync
+ // to a timestamp on the same clock as QueryPerformanceCounter.
+ DXGI_FRAME_STATISTICS stats;
+ hr = IDXGISwapChain_GetFrameStatistics(p->swapchain, &stats);
+ if (hr == DXGI_ERROR_FRAME_STATISTICS_DISJOINT) {
+ p->last_sync_refresh_count = 0;
+ p->last_sync_qpc_time = 0;
+ p->vsync_duration_qpc = 0;
+ }
+ if (FAILED(hr))
+ return;
+
+ // Detecting skipped vsyncs is possible but not supported yet
+ info->skipped_vsyncs = 0;
+
+ // Get the number of physical vsyncs that have passed since the last call.
+ // Check for 0 here, since sometimes GetFrameStatistics returns S_OK but
+ // with 0s in some (all?) members of DXGI_FRAME_STATISTICS.
+ unsigned src_passed = 0;
+ if (stats.SyncRefreshCount && p->last_sync_refresh_count)
+ src_passed = stats.SyncRefreshCount - p->last_sync_refresh_count;
+ p->last_sync_refresh_count = stats.SyncRefreshCount;
+
+ // Get the elapsed time passed between the above vsyncs
+ unsigned sqt_passed = 0;
+ if (stats.SyncQPCTime.QuadPart && p->last_sync_qpc_time)
+ sqt_passed = stats.SyncQPCTime.QuadPart - p->last_sync_qpc_time;
+ p->last_sync_qpc_time = stats.SyncQPCTime.QuadPart;
+
+ // If any vsyncs have passed, estimate the physical frame rate
+ if (src_passed && sqt_passed)
+ p->vsync_duration_qpc = sqt_passed / src_passed;
+ if (p->vsync_duration_qpc)
+ info->vsync_duration = qpc_to_us(sw, p->vsync_duration_qpc);
+
+ // If the physical frame rate is known and the other members of
+ // DXGI_FRAME_STATISTICS are non-0, estimate the timing of the next frame
+ if (p->vsync_duration_qpc && stats.PresentCount &&
+ stats.PresentRefreshCount && stats.SyncRefreshCount &&
+ stats.SyncQPCTime.QuadPart)
+ {
+ // PresentRefreshCount and SyncRefreshCount might refer to different
+ // frames (this can definitely occur in bitblt-mode.) Assuming mpv
+ // presents on every frame, guess the present count that relates to
+ // SyncRefreshCount.
+ unsigned expected_sync_pc = stats.PresentCount +
+ (stats.SyncRefreshCount - stats.PresentRefreshCount);
+
+ // Now guess the timestamp of the last submitted frame based on the
+ // timestamp of the frame at SyncRefreshCount and the frame rate
+ int64_t last_queue_display_time_qpc = stats.SyncQPCTime.QuadPart +
+ (submit_count - expected_sync_pc) * p->vsync_duration_qpc;
+
+ // Only set the estimated display time if it's after the last submission
+ // time. It could be before if mpv skips a lot of frames.
+ if (last_queue_display_time_qpc >= p->last_submit_qpc) {
+ info->last_queue_display_time = mp_time_us() +
+ (qpc_to_us(sw, last_queue_display_time_qpc) - qpc_us_now(sw));
+ }
+ }
+}
+
static int d3d11_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
+ struct priv *p = ctx->priv;
+
int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (request == VOCTRL_RESUME) {
+ // Reset frame statistics after pause
+ p->last_sync_refresh_count = 0;
+ p->last_sync_qpc_time = 0;
+ p->vsync_duration_qpc = 0;
+ }
if (*events & VO_EVENT_RESIZE) {
if (!resize(ctx))
return VO_ERROR;
@@ -178,6 +297,7 @@ static const struct ra_swapchain_fns d3d11_swapchain = {
.start_frame = d3d11_start_frame,
.submit_frame = d3d11_submit_frame,
.swap_buffers = d3d11_swap_buffers,
+ .get_vsync = d3d11_get_vsync,
};
static bool d3d11_init(struct ra_ctx *ctx)
@@ -185,6 +305,10 @@ static bool d3d11_init(struct ra_ctx *ctx)
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->opts = mp_get_config_group(ctx, ctx->global, &d3d11_conf);
+ LARGE_INTEGER perf_freq;
+ QueryPerformanceFrequency(&perf_freq);
+ p->perf_freq = perf_freq.QuadPart;
+
struct ra_swapchain *sw = ctx->swapchain = talloc_zero(ctx, struct ra_swapchain);
sw->priv = p;
sw->ctx = ctx;