6#include <QWaitCondition>
7#include <QElapsedTimer>
8#include <QCoreApplication>
9#include <QLoggingCategory>
11#include <QVarLengthArray>
12#include <QtCore/private/qsystemerror_p.h>
16Q_STATIC_LOGGING_CATEGORY(lcQpaScreenUpdates,
"qt.qpa.screen.updates", QtCriticalMsg);
23 QDxgiVSyncThread(IDXGIOutput *output,
float vsyncIntervalMsReportedForScreen, Callback callback);
29 float vsyncIntervalMsReportedForScreen;
39 vsyncIntervalMsReportedForScreen(vsyncIntervalMsReportedForScreen),
42 DXGI_OUTPUT_DESC desc;
43 output->GetDesc(&desc);
44 monitor = desc.Monitor;
49 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"for output" << output <<
"monitor" << monitor <<
"entered run()";
50 QElapsedTimer timestamp;
51 QElapsedTimer elapsed;
53 while (!quit.loadAcquire()) {
55 HRESULT hr = output->WaitForVBlank();
56 if (FAILED(hr) || elapsed.nsecsElapsed() <= 1000000) {
61 QThread::msleep((
unsigned long) vsyncIntervalMsReportedForScreen);
63 callback(output, monitor, timestamp.nsecsElapsed());
66 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"is stopping";
70 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"run() out";
76 qCDebug(lcQpaScreenUpdates) <<
"Requesting QDxgiVSyncThread stop from thread" << QThread::currentThread() <<
"on" <<
this;
77 if (isRunning() && !quit.loadAcquire()) {
85QDxgiVSyncService *QDxgiVSyncService::instance()
87 static QDxgiVSyncService service;
91QDxgiVSyncService::QDxgiVSyncService()
93 qCDebug(lcQpaScreenUpdates) <<
"New QDxgiVSyncService" <<
this;
95 disableService = qEnvironmentVariableIntValue(
"QT_D3D_NO_VBLANK_THREAD");
97 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService disabled by environment";
102QDxgiVSyncService::~QDxgiVSyncService()
104 qCDebug(lcQpaScreenUpdates) <<
"~QDxgiVSyncService" <<
this;
109 qWarning(
"QDxgiVSyncService not destroyed in time");
112void QDxgiVSyncService::global_destroy()
114 QDxgiVSyncService *inst = QDxgiVSyncService::instance();
115 inst->cleanupRegistered =
false;
119void QDxgiVSyncService::destroy()
121 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::destroy()";
126 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
127 cleanupWindowData(&*it);
133void QDxgiVSyncService::teardownDxgi()
135 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
136 cleanupAdapterData(&*it);
140 dxgiFactory->Release();
141 dxgiFactory =
nullptr;
144 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService DXGI teardown complete";
147void QDxgiVSyncService::beginFrame(LUID)
149 QMutexLocker lock(&mutex);
161 if (dxgiFactory && !dxgiFactory->IsCurrent()) {
162 qWarning(
"QDxgiVSyncService: DXGI Factory is no longer Current");
163 QVarLengthArray<LUID, 8> luids;
164 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
165 luids.append(it->luid);
166 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
167 cleanupWindowData(&*it);
170 for (LUID luid : luids)
173 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
174 updateWindowData(it.key(), &*it);
178void QDxgiVSyncService::refAdapter(LUID luid)
180 QMutexLocker lock(&mutex);
185 HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2),
reinterpret_cast<
void **>(&dxgiFactory));
187 disableService =
true;
188 qWarning(
"QDxgiVSyncService: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr)));
191 if (!cleanupRegistered) {
192 qAddPostRoutine(QDxgiVSyncService::global_destroy);
193 cleanupRegistered =
true;
197 for (AdapterData &a : adapters) {
198 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
210 for (
int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &ad) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
211 DXGI_ADAPTER_DESC1 desc;
213 if (desc.AdapterLuid.LowPart == luid.LowPart && desc.AdapterLuid.HighPart == luid.HighPart) {
221 qWarning(
"VSyncService: Failed to find adapter (via EnumAdapters1), skipping");
227 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService refAdapter for not yet seen adapter" << luid.LowPart << luid.HighPart;
230 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
231 updateWindowData(it.key(), &*it);
234void QDxgiVSyncService::derefAdapter(LUID luid)
236 QVarLengthArray<AdapterData, 4> cleanupList;
239 QMutexLocker lock(&mutex);
243 for (qsizetype i = 0; i < adapters.count(); ++i) {
244 AdapterData &a(adapters[i]);
245 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
247 cleanupList.append(a);
248 adapters.removeAt(i);
256 for (AdapterData &a : cleanupList)
257 cleanupAdapterData(&a);
260void QDxgiVSyncService::cleanupAdapterData(AdapterData *a)
262 for (
auto it = a->notifiers.begin(), end = a->notifiers.end(); it != end; ++it) {
263 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): about to call stop()";
265 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): stop() called";
267 it->output->Release();
269 a->notifiers.clear();
271 a->adapter->Release();
272 a->adapter =
nullptr;
275void QDxgiVSyncService::cleanupWindowData(WindowData *w)
278 w->output->Release();
290 QRect wr = w->geometry();
291 wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
292 const QPoint center = wr.center();
293 IDXGIOutput *currentOutput =
nullptr;
294 IDXGIOutput *output =
nullptr;
295 for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
296 DXGI_OUTPUT_DESC desc;
297 output->GetDesc(&desc);
298 const RECT r = desc.DesktopCoordinates;
299 const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
300 if (dr.contains(center)) {
301 currentOutput = output;
307 return currentOutput;
310void QDxgiVSyncService::updateWindowData(QWindow *window, WindowData *wd)
312 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it) {
313 IDXGIOutput *output = outputForWindow(window, it->adapter);
321 DXGI_OUTPUT_DESC desc;
322 output->GetDesc(&desc);
324 if (wd->output && wd->output != output) {
325 if (desc.Monitor == wd->monitor) {
329 wd->output->Release();
333 wd->monitor = desc.Monitor;
335 QScreen *screen = window->screen();
336 const qreal refresh = screen ? screen->refreshRate() : 60;
337 wd->reportedRefreshIntervalMs = refresh > 0 ? 1000.0f /
float(refresh) : 1000.f / 60.0f;
339 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: Output for window" << window
340 <<
"on the actively used adapters is now" << output
341 <<
"HMONITOR" << wd->monitor
342 <<
"refresh" << wd->reportedRefreshIntervalMs;
344 if (!it->notifiers.contains(wd->monitor)) {
346 QDxgiVSyncThread *t =
new QDxgiVSyncThread(output, wd->reportedRefreshIntervalMs,
347 [
this](IDXGIOutput *, HMONITOR monitor, qint64 timestampNs) {
348 CallbackWindowList w;
349 QMutexLocker lock(&mutex);
350 for (
auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
351 if (it->output && it->monitor == monitor)
356 qDebug() <<
"vsync thread" << QThread::currentThread() << monitor <<
"window list" << w << timestampNs;
358 for (
const Callback &cb : std::as_const(callbacks)) {
364 t->start(QThread::TimeCriticalPriority);
365 it->notifiers.insert(wd->monitor, { wd->output, t });
374void QDxgiVSyncService::registerWindow(QWindow *window)
376 QMutexLocker lock(&mutex);
377 if (disableService || windows.contains(window))
380 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: adding window" << window;
384 updateWindowData(window, &wd);
385 windows.insert(window, wd);
387 QObject::connect(window, &QWindow::screenChanged, window, [
this, window](QScreen *screen) {
388 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: screen changed for window:" << window << screen;
389 QMutexLocker lock(&mutex);
390 auto it = windows.find(window);
391 if (it != windows.end())
392 updateWindowData(window, &*it);
393 }, Qt::QueuedConnection);
399void QDxgiVSyncService::unregisterWindow(QWindow *window)
401 QMutexLocker lock(&mutex);
402 auto it = windows.find(window);
403 if (it == windows.end())
406 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: removing window" << window;
408 cleanupWindowData(&*it);
410 windows.remove(window);
413bool QDxgiVSyncService::supportsWindow(QWindow *window)
415 QMutexLocker lock(&mutex);
416 auto it = windows.constFind(window);
417 return it != windows.cend() ? (it->output !=
nullptr) :
false;
420qsizetype QDxgiVSyncService::registerCallback(Callback cb)
422 QMutexLocker lock(&mutex);
423 for (qsizetype i = 0; i < callbacks.count(); ++i) {
429 callbacks.append(cb);
430 return callbacks.count();
433void QDxgiVSyncService::unregisterCallback(qsizetype id)
435 QMutexLocker lock(&mutex);
436 const qsizetype index = id - 1;
437 if (index >= 0 && index < callbacks.count())
438 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)