7#include <QWaitCondition>
8#include <QElapsedTimer>
9#include <QCoreApplication>
10#include <QLoggingCategory>
12#include <QVarLengthArray>
13#include <QtCore/private/qsystemerror_p.h>
17using namespace Qt::StringLiterals;
26 QDxgiVSyncThread(IDXGIOutput *output,
float vsyncIntervalMsReportedForScreen, Callback callback);
32 float vsyncIntervalMsReportedForScreen;
42 vsyncIntervalMsReportedForScreen(vsyncIntervalMsReportedForScreen),
45 QThread::setObjectName(u"QDxgiVSyncThread"_s);
47 DXGI_OUTPUT_DESC desc;
48 output->GetDesc(&desc);
49 monitor = desc.Monitor;
54 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"for output" << output <<
"monitor" << monitor <<
"entered run()";
55 QElapsedTimer timestamp;
56 QElapsedTimer elapsed;
58 while (!quit.loadAcquire()) {
60 HRESULT hr = output->WaitForVBlank();
61 if (FAILED(hr) || elapsed.nsecsElapsed() <= 1000000) {
66 QThread::msleep((
unsigned long) vsyncIntervalMsReportedForScreen);
68 callback(output, monitor, timestamp.nsecsElapsed());
71 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"is stopping";
75 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"run() out";
81 qCDebug(lcQpaScreenUpdates) <<
"Requesting QDxgiVSyncThread stop from thread" << QThread::currentThread() <<
"on" <<
this;
82 if (isRunning() && !quit.loadAcquire()) {
90QDxgiVSyncService *QDxgiVSyncService::instance()
92 static QDxgiVSyncService service;
96QDxgiVSyncService::QDxgiVSyncService()
98 qCDebug(lcQpaScreenUpdates) <<
"New QDxgiVSyncService" <<
this;
100 disableService = qEnvironmentVariableIntValue(
"QT_D3D_NO_VBLANK_THREAD");
101 if (disableService) {
102 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService disabled by environment";
107QDxgiVSyncService::~QDxgiVSyncService()
109 qCDebug(lcQpaScreenUpdates) <<
"~QDxgiVSyncService" <<
this;
114 qWarning(
"QDxgiVSyncService not destroyed in time");
117void QDxgiVSyncService::global_destroy()
119 QDxgiVSyncService *inst = QDxgiVSyncService::instance();
120 inst->cleanupRegistered =
false;
124void QDxgiVSyncService::destroy()
126 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::destroy()";
131 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
132 cleanupWindowData(&*it);
138void QDxgiVSyncService::teardownDxgi()
140 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
141 cleanupAdapterData(&*it);
145 dxgiFactory->Release();
146 dxgiFactory =
nullptr;
149 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService DXGI teardown complete";
152void QDxgiVSyncService::beginFrame(LUID)
154 QMutexLocker lock(&mutex);
166 if (dxgiFactory && !dxgiFactory->IsCurrent()) {
167 qCDebug(lcQpaScreenUpdates,
"QDxgiVSyncService: DXGI Factory is no longer Current");
168 QVarLengthArray<LUID, 8> luids;
169 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
170 luids.append(it->luid);
171 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
172 cleanupWindowData(&*it);
175 for (LUID luid : luids)
178 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
179 updateWindowData(it.key(), &*it);
183void QDxgiVSyncService::refAdapter(LUID luid)
185 QMutexLocker lock(&mutex);
190 HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2),
reinterpret_cast<
void **>(&dxgiFactory));
192 disableService =
true;
193 qWarning(
"QDxgiVSyncService: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr)));
196 if (!cleanupRegistered) {
197 qAddPostRoutine(QDxgiVSyncService::global_destroy);
198 cleanupRegistered =
true;
202 for (AdapterData &a : adapters) {
203 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
215 for (
int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &ad) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
216 DXGI_ADAPTER_DESC1 desc;
218 if (desc.AdapterLuid.LowPart == luid.LowPart && desc.AdapterLuid.HighPart == luid.HighPart) {
226 qWarning(
"VSyncService: Failed to find adapter (via EnumAdapters1), skipping");
232 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService refAdapter for not yet seen adapter" << luid.LowPart << luid.HighPart;
235 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
236 updateWindowData(it.key(), &*it);
239void QDxgiVSyncService::derefAdapter(LUID luid)
241 QVarLengthArray<AdapterData, 4> cleanupList;
244 QMutexLocker lock(&mutex);
248 for (qsizetype i = 0; i < adapters.count(); ++i) {
249 AdapterData &a(adapters[i]);
250 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
252 cleanupList.append(a);
253 adapters.removeAt(i);
261 for (AdapterData &a : cleanupList)
262 cleanupAdapterData(&a);
265void QDxgiVSyncService::cleanupAdapterData(AdapterData *a)
267 for (
auto it = a->notifiers.begin(), end = a->notifiers.end(); it != end; ++it) {
268 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): about to call stop()";
270 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): stop() called";
272 it->output->Release();
274 a->notifiers.clear();
276 a->adapter->Release();
277 a->adapter =
nullptr;
280void QDxgiVSyncService::cleanupWindowData(WindowData *w)
283 w->output->Release();
295 HMONITOR monitor =
nullptr;
299 monitor = MonitorFromWindow(
reinterpret_cast<HWND>(w->winId()), MONITOR_DEFAULTTONEAREST);
301 QRect wr = w->geometry();
302 wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
303 center = wr.center();
305 IDXGIOutput *currentOutput =
nullptr;
306 IDXGIOutput *output =
nullptr;
307 for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
308 DXGI_OUTPUT_DESC desc;
309 if (FAILED(output->GetDesc(&desc))) {
313 const RECT r = desc.DesktopCoordinates;
314 const QRect dr(QPoint(r.left, r.top), QSize(r.right - r.left, r.bottom - r.top));
315 if ((monitor && desc.Monitor == monitor) || (!monitor && dr.contains(center))) {
316 currentOutput = output;
322 return currentOutput;
325void QDxgiVSyncService::updateWindowData(QWindow *window, WindowData *wd)
327 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it) {
328 IDXGIOutput *output = outputForWindow(window, it->adapter);
336 DXGI_OUTPUT_DESC desc;
337 output->GetDesc(&desc);
339 if (wd->output && wd->output != output) {
340 if (desc.Monitor == wd->monitor) {
344 wd->output->Release();
348 wd->monitor = desc.Monitor;
350 QScreen *screen = window->screen();
351 const qreal refresh = screen ? screen->refreshRate() : 60;
352 wd->reportedRefreshIntervalMs = refresh > 0 ? 1000.0f /
float(refresh) : 1000.f / 60.0f;
354 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: Output for window" << window
355 <<
"on the actively used adapters is now" << output
356 <<
"HMONITOR" << wd->monitor
357 <<
"refresh" << wd->reportedRefreshIntervalMs;
359 if (!it->notifiers.contains(wd->monitor)) {
361 QDxgiVSyncThread *t =
new QDxgiVSyncThread(output, wd->reportedRefreshIntervalMs,
362 [
this](IDXGIOutput *, HMONITOR monitor, qint64 timestampNs) {
363 CallbackWindowList w;
364 QMutexLocker lock(&mutex);
365 for (
auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
366 if (it->output && it->monitor == monitor)
371 qDebug() <<
"vsync thread" << QThread::currentThread() << monitor <<
"window list" << w << timestampNs;
373 for (
const Callback &cb : std::as_const(callbacks)) {
379 t->start(QThread::TimeCriticalPriority);
380 it->notifiers.insert(wd->monitor, { wd->output, t });
389void QDxgiVSyncService::registerWindow(QWindow *window)
391 QMutexLocker lock(&mutex);
392 if (disableService || windows.contains(window))
395 if (window->format().swapInterval() == 0) {
396 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: window" << window
397 <<
"has swapInterval 0, skipping this window";
401 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: adding window" << window;
405 updateWindowData(window, &wd);
406 windows.insert(window, wd);
408 QObject::connect(window, &QWindow::screenChanged, window, [
this, window](QScreen *screen) {
409 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: screen changed for window:" << window << screen;
410 QMutexLocker lock(&mutex);
411 auto it = windows.find(window);
412 if (it != windows.end())
413 updateWindowData(window, &*it);
414 }, Qt::QueuedConnection);
423 QObject::connect(window, &QObject::destroyed, window, [
this, window](QObject *) {
424 qCDebug(lcQpaScreenUpdates)
425 <<
"QDxgiVSyncService: window destroyed, pruning stale registration for:"
427 unregisterWindow(window);
428 }, Qt::DirectConnection);
431void QDxgiVSyncService::unregisterWindow(QWindow *window)
433 QMutexLocker lock(&mutex);
434 auto it = windows.find(window);
435 if (it == windows.end())
438 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: removing window" << window;
440 cleanupWindowData(&*it);
442 windows.remove(window);
445bool QDxgiVSyncService::supportsWindow(QWindow *window)
447 QMutexLocker lock(&mutex);
448 auto it = windows.constFind(window);
449 return it != windows.cend() ? (it->output !=
nullptr) :
false;
452qsizetype QDxgiVSyncService::registerCallback(Callback cb)
454 QMutexLocker lock(&mutex);
455 for (qsizetype i = 0; i < callbacks.count(); ++i) {
461 callbacks.append(cb);
462 return callbacks.count();
465void QDxgiVSyncService::unregisterCallback(qsizetype id)
467 QMutexLocker lock(&mutex);
468 const qsizetype index = id - 1;
469 if (index >= 0 && index < callbacks.count())
470 callbacks[index] =
nullptr;
QDxgiVSyncThread(IDXGIOutput *output, float vsyncIntervalMsReportedForScreen, Callback callback)
Combined button and popup list for selecting options.
static IDXGIOutput * outputForWindow(QWindow *w, IDXGIAdapter *adapter)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)