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 QRect wr = w->geometry();
296 wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
297 const QPoint center = wr.center();
298 IDXGIOutput *currentOutput =
nullptr;
299 IDXGIOutput *output =
nullptr;
300 for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
301 DXGI_OUTPUT_DESC desc;
302 output->GetDesc(&desc);
303 const RECT r = desc.DesktopCoordinates;
304 const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
305 if (dr.contains(center)) {
306 currentOutput = output;
312 return currentOutput;
315void QDxgiVSyncService::updateWindowData(QWindow *window, WindowData *wd)
317 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it) {
318 IDXGIOutput *output = outputForWindow(window, it->adapter);
326 DXGI_OUTPUT_DESC desc;
327 output->GetDesc(&desc);
329 if (wd->output && wd->output != output) {
330 if (desc.Monitor == wd->monitor) {
334 wd->output->Release();
338 wd->monitor = desc.Monitor;
340 QScreen *screen = window->screen();
341 const qreal refresh = screen ? screen->refreshRate() : 60;
342 wd->reportedRefreshIntervalMs = refresh > 0 ? 1000.0f /
float(refresh) : 1000.f / 60.0f;
344 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: Output for window" << window
345 <<
"on the actively used adapters is now" << output
346 <<
"HMONITOR" << wd->monitor
347 <<
"refresh" << wd->reportedRefreshIntervalMs;
349 if (!it->notifiers.contains(wd->monitor)) {
351 QDxgiVSyncThread *t =
new QDxgiVSyncThread(output, wd->reportedRefreshIntervalMs,
352 [
this](IDXGIOutput *, HMONITOR monitor, qint64 timestampNs) {
353 CallbackWindowList w;
354 QMutexLocker lock(&mutex);
355 for (
auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
356 if (it->output && it->monitor == monitor)
361 qDebug() <<
"vsync thread" << QThread::currentThread() << monitor <<
"window list" << w << timestampNs;
363 for (
const Callback &cb : std::as_const(callbacks)) {
369 t->start(QThread::TimeCriticalPriority);
370 it->notifiers.insert(wd->monitor, { wd->output, t });
379void QDxgiVSyncService::registerWindow(QWindow *window)
381 QMutexLocker lock(&mutex);
382 if (disableService || windows.contains(window))
385 if (window->format().swapInterval() == 0) {
386 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: window" << window
387 <<
"has swapInterval 0, skipping this window";
391 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: adding window" << window;
395 updateWindowData(window, &wd);
396 windows.insert(window, wd);
398 QObject::connect(window, &QWindow::screenChanged, window, [
this, window](QScreen *screen) {
399 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: screen changed for window:" << window << screen;
400 QMutexLocker lock(&mutex);
401 auto it = windows.find(window);
402 if (it != windows.end())
403 updateWindowData(window, &*it);
404 }, Qt::QueuedConnection);
410void QDxgiVSyncService::unregisterWindow(QWindow *window)
412 QMutexLocker lock(&mutex);
413 auto it = windows.find(window);
414 if (it == windows.end())
417 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: removing window" << window;
419 cleanupWindowData(&*it);
421 windows.remove(window);
424bool QDxgiVSyncService::supportsWindow(QWindow *window)
426 QMutexLocker lock(&mutex);
427 auto it = windows.constFind(window);
428 return it != windows.cend() ? (it->output !=
nullptr) :
false;
431qsizetype QDxgiVSyncService::registerCallback(Callback cb)
433 QMutexLocker lock(&mutex);
434 for (qsizetype i = 0; i < callbacks.count(); ++i) {
440 callbacks.append(cb);
441 return callbacks.count();
444void QDxgiVSyncService::unregisterCallback(qsizetype id)
446 QMutexLocker lock(&mutex);
447 const qsizetype index = id - 1;
448 if (index >= 0 && index < callbacks.count())
449 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)