7#include <QWaitCondition>
8#include <QElapsedTimer>
9#include <QCoreApplication>
10#include <QLoggingCategory>
12#include <QVarLengthArray>
13#include <QtCore/private/qsystemerror_p.h>
17Q_STATIC_LOGGING_CATEGORY(lcQpaScreenUpdates,
"qt.qpa.screen.updates", QtCriticalMsg);
24 QDxgiVSyncThread(IDXGIOutput *output,
float vsyncIntervalMsReportedForScreen, Callback callback);
30 float vsyncIntervalMsReportedForScreen;
40 vsyncIntervalMsReportedForScreen(vsyncIntervalMsReportedForScreen),
43 DXGI_OUTPUT_DESC desc;
44 output->GetDesc(&desc);
45 monitor = desc.Monitor;
50 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"for output" << output <<
"monitor" << monitor <<
"entered run()";
51 QElapsedTimer timestamp;
52 QElapsedTimer elapsed;
54 while (!quit.loadAcquire()) {
56 HRESULT hr = output->WaitForVBlank();
57 if (FAILED(hr) || elapsed.nsecsElapsed() <= 1000000) {
62 QThread::msleep((
unsigned long) vsyncIntervalMsReportedForScreen);
64 callback(output, monitor, timestamp.nsecsElapsed());
67 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"is stopping";
71 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncThread" <<
this <<
"run() out";
77 qCDebug(lcQpaScreenUpdates) <<
"Requesting QDxgiVSyncThread stop from thread" << QThread::currentThread() <<
"on" <<
this;
78 if (isRunning() && !quit.loadAcquire()) {
86QDxgiVSyncService *QDxgiVSyncService::instance()
88 static QDxgiVSyncService service;
92QDxgiVSyncService::QDxgiVSyncService()
94 qCDebug(lcQpaScreenUpdates) <<
"New QDxgiVSyncService" <<
this;
96 disableService = qEnvironmentVariableIntValue(
"QT_D3D_NO_VBLANK_THREAD");
98 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService disabled by environment";
103QDxgiVSyncService::~QDxgiVSyncService()
105 qCDebug(lcQpaScreenUpdates) <<
"~QDxgiVSyncService" <<
this;
110 qWarning(
"QDxgiVSyncService not destroyed in time");
113void QDxgiVSyncService::global_destroy()
115 QDxgiVSyncService *inst = QDxgiVSyncService::instance();
116 inst->cleanupRegistered =
false;
120void QDxgiVSyncService::destroy()
122 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::destroy()";
127 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
128 cleanupWindowData(&*it);
134void QDxgiVSyncService::teardownDxgi()
136 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
137 cleanupAdapterData(&*it);
141 dxgiFactory->Release();
142 dxgiFactory =
nullptr;
145 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService DXGI teardown complete";
148void QDxgiVSyncService::beginFrame(LUID)
150 QMutexLocker lock(&mutex);
162 if (dxgiFactory && !dxgiFactory->IsCurrent()) {
163 qCDebug(lcQpaScreenUpdates,
"QDxgiVSyncService: DXGI Factory is no longer Current");
164 QVarLengthArray<LUID, 8> luids;
165 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it)
166 luids.append(it->luid);
167 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
168 cleanupWindowData(&*it);
171 for (LUID luid : luids)
174 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
175 updateWindowData(it.key(), &*it);
179void QDxgiVSyncService::refAdapter(LUID luid)
181 QMutexLocker lock(&mutex);
186 HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2),
reinterpret_cast<
void **>(&dxgiFactory));
188 disableService =
true;
189 qWarning(
"QDxgiVSyncService: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr)));
192 if (!cleanupRegistered) {
193 qAddPostRoutine(QDxgiVSyncService::global_destroy);
194 cleanupRegistered =
true;
198 for (AdapterData &a : adapters) {
199 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
211 for (
int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &ad) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
212 DXGI_ADAPTER_DESC1 desc;
214 if (desc.AdapterLuid.LowPart == luid.LowPart && desc.AdapterLuid.HighPart == luid.HighPart) {
222 qWarning(
"VSyncService: Failed to find adapter (via EnumAdapters1), skipping");
228 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService refAdapter for not yet seen adapter" << luid.LowPart << luid.HighPart;
231 for (
auto it = windows.begin(), end = windows.end(); it != end; ++it)
232 updateWindowData(it.key(), &*it);
235void QDxgiVSyncService::derefAdapter(LUID luid)
237 QVarLengthArray<AdapterData, 4> cleanupList;
240 QMutexLocker lock(&mutex);
244 for (qsizetype i = 0; i < adapters.count(); ++i) {
245 AdapterData &a(adapters[i]);
246 if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) {
248 cleanupList.append(a);
249 adapters.removeAt(i);
257 for (AdapterData &a : cleanupList)
258 cleanupAdapterData(&a);
261void QDxgiVSyncService::cleanupAdapterData(AdapterData *a)
263 for (
auto it = a->notifiers.begin(), end = a->notifiers.end(); it != end; ++it) {
264 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): about to call stop()";
266 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService::cleanupAdapterData(): stop() called";
268 it->output->Release();
270 a->notifiers.clear();
272 a->adapter->Release();
273 a->adapter =
nullptr;
276void QDxgiVSyncService::cleanupWindowData(WindowData *w)
279 w->output->Release();
291 QRect wr = w->geometry();
292 wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
293 const QPoint center = wr.center();
294 IDXGIOutput *currentOutput =
nullptr;
295 IDXGIOutput *output =
nullptr;
296 for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
297 DXGI_OUTPUT_DESC desc;
298 output->GetDesc(&desc);
299 const RECT r = desc.DesktopCoordinates;
300 const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
301 if (dr.contains(center)) {
302 currentOutput = output;
308 return currentOutput;
311void QDxgiVSyncService::updateWindowData(QWindow *window, WindowData *wd)
313 for (
auto it = adapters.begin(), end = adapters.end(); it != end; ++it) {
314 IDXGIOutput *output = outputForWindow(window, it->adapter);
322 DXGI_OUTPUT_DESC desc;
323 output->GetDesc(&desc);
325 if (wd->output && wd->output != output) {
326 if (desc.Monitor == wd->monitor) {
330 wd->output->Release();
334 wd->monitor = desc.Monitor;
336 QScreen *screen = window->screen();
337 const qreal refresh = screen ? screen->refreshRate() : 60;
338 wd->reportedRefreshIntervalMs = refresh > 0 ? 1000.0f /
float(refresh) : 1000.f / 60.0f;
340 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: Output for window" << window
341 <<
"on the actively used adapters is now" << output
342 <<
"HMONITOR" << wd->monitor
343 <<
"refresh" << wd->reportedRefreshIntervalMs;
345 if (!it->notifiers.contains(wd->monitor)) {
347 QDxgiVSyncThread *t =
new QDxgiVSyncThread(output, wd->reportedRefreshIntervalMs,
348 [
this](IDXGIOutput *, HMONITOR monitor, qint64 timestampNs) {
349 CallbackWindowList w;
350 QMutexLocker lock(&mutex);
351 for (
auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
352 if (it->output && it->monitor == monitor)
357 qDebug() <<
"vsync thread" << QThread::currentThread() << monitor <<
"window list" << w << timestampNs;
359 for (
const Callback &cb : std::as_const(callbacks)) {
365 t->start(QThread::TimeCriticalPriority);
366 it->notifiers.insert(wd->monitor, { wd->output, t });
375void QDxgiVSyncService::registerWindow(QWindow *window)
377 QMutexLocker lock(&mutex);
378 if (disableService || windows.contains(window))
381 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: adding window" << window;
385 updateWindowData(window, &wd);
386 windows.insert(window, wd);
388 QObject::connect(window, &QWindow::screenChanged, window, [
this, window](QScreen *screen) {
389 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: screen changed for window:" << window << screen;
390 QMutexLocker lock(&mutex);
391 auto it = windows.find(window);
392 if (it != windows.end())
393 updateWindowData(window, &*it);
394 }, Qt::QueuedConnection);
400void QDxgiVSyncService::unregisterWindow(QWindow *window)
402 QMutexLocker lock(&mutex);
403 auto it = windows.find(window);
404 if (it == windows.end())
407 qCDebug(lcQpaScreenUpdates) <<
"QDxgiVSyncService: removing window" << window;
409 cleanupWindowData(&*it);
411 windows.remove(window);
414bool QDxgiVSyncService::supportsWindow(QWindow *window)
416 QMutexLocker lock(&mutex);
417 auto it = windows.constFind(window);
418 return it != windows.cend() ? (it->output !=
nullptr) :
false;
421qsizetype QDxgiVSyncService::registerCallback(Callback cb)
423 QMutexLocker lock(&mutex);
424 for (qsizetype i = 0; i < callbacks.count(); ++i) {
430 callbacks.append(cb);
431 return callbacks.count();
434void QDxgiVSyncService::unregisterCallback(qsizetype id)
436 QMutexLocker lock(&mutex);
437 const qsizetype index = id - 1;
438 if (index >= 0 && index < callbacks.count())
439 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)