dxgi: emulate IDXGIOutput::WaitForVBlank() (Wine bug 56799). Unblocks JUCE 7 plugin GUIs. Applies to Wine 11.19. From https://pr1v8.ca, LGPL-2.1-or-later like Wine. Subject: [PATCH] dxgi: Emulate IDXGIOutput::WaitForVBlank() from the output refresh rate. JUCE 7 (VSyncThread) and Qt 6.9 drive their repaints from a thread that loops on IDXGIOutput::WaitForVBlank() and only dispatches deferred repaints when it returns S_OK. With the E_NOTIMPL stub their windows paint once and then never update (Wine bug 56799). Sleep until the next refresh-period boundary of the output's current display mode, as DXVK does, instead of busy-polling the emulated raster status. --- dlls/dxgi/output.c | 46 ++++++++++++++++++++++++++++---- dlls/dxgi/tests/dxgi.c | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/dlls/dxgi/output.c b/dlls/dxgi/output.c index e828ed0..ef0cf04 100644 --- a/dlls/dxgi/output.c +++ b/dlls/dxgi/output.c @@ -17,6 +17,7 @@ */ #include "dxgi_private.h" +#include "winternl.h" WINE_DEFAULT_DEBUG_CHANNEL(dxgi); @@ -363,14 +364,49 @@ static HRESULT STDMETHODCALLTYPE dxgi_output_FindClosestMatchingMode(IDXGIOutput static HRESULT STDMETHODCALLTYPE dxgi_output_WaitForVBlank(IDXGIOutput6 *iface) { - static BOOL once = FALSE; + struct dxgi_output *output = impl_from_IDXGIOutput6(iface); + static LARGE_INTEGER frequency; + struct wined3d_display_mode mode; + enum wined3d_display_rotation rotation; + LARGE_INTEGER now, timeout; + unsigned int refresh_rate = 0; + ULONGLONG period, next; + static BOOL once; + TRACE("iface %p.\n", iface); + + /* There is no portable way to wait for the real vertical blank here. + * Emulate it by sleeping until the next refresh interval boundary of the + * output's current display mode, measured on the performance counter, so + * that callers which pace their redraws with this call (e.g. JUCE 7's + * VSyncThread) keep running instead of spinning on E_NOTIMPL. */ if (!once++) - FIXME("iface %p stub!\n", iface); - else - TRACE("iface %p stub!\n", iface); + FIXME("iface %p, emulating vertical blank from the display mode refresh rate.\n", iface); - return E_NOTIMPL; + wined3d_mutex_lock(); + if (SUCCEEDED(wined3d_output_get_display_mode(output->wined3d_output, &mode, &rotation))) + refresh_rate = mode.refresh_rate; + wined3d_mutex_unlock(); + + if (refresh_rate < 24 || refresh_rate > 1000) + refresh_rate = 60; + + if (!frequency.QuadPart) + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&now); + + period = frequency.QuadPart / refresh_rate; + if (!period) + period = 1; + next = (now.QuadPart / period + 1) * period; + + /* Relative timeout in 100ns units. */ + timeout.QuadPart = -(LONGLONG)(((next - now.QuadPart) * 10000000) / frequency.QuadPart); + if (!timeout.QuadPart) + timeout.QuadPart = -1; + NtDelayExecution(FALSE, &timeout); + + return S_OK; } static HRESULT STDMETHODCALLTYPE dxgi_output_TakeOwnership(IDXGIOutput6 *iface, IUnknown *device, BOOL exclusive) diff --git a/dlls/dxgi/tests/dxgi.c b/dlls/dxgi/tests/dxgi.c index 59ed17f..80cafa9 100644 --- a/dlls/dxgi/tests/dxgi.c +++ b/dlls/dxgi/tests/dxgi.c @@ -5960,6 +5960,64 @@ static void test_maximum_frame_latency(void) ok(!refcount, "Device has %lu references left.\n", refcount); } +static void test_output_wait_for_vblank(void) +{ + LARGE_INTEGER frequency, start, end; + DXGI_OUTPUT_DESC output_desc; + IDXGIFactory *factory; + IDXGIAdapter *adapter; + IDXGIOutput *output; + double elapsed_ms; + DEVMODEW mode; + unsigned int i; + HRESULT hr; + + hr = CreateDXGIFactory(&IID_IDXGIFactory, (void **)&factory); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + if (IDXGIFactory_EnumAdapters(factory, 0, &adapter) == DXGI_ERROR_NOT_FOUND) + { + skip("Adapter not found.\n"); + IDXGIFactory_Release(factory); + return; + } + if (IDXGIAdapter_EnumOutputs(adapter, 0, &output) == DXGI_ERROR_NOT_FOUND) + { + skip("Output not found.\n"); + IDXGIAdapter_Release(adapter); + IDXGIFactory_Release(factory); + return; + } + + hr = IDXGIOutput_GetDesc(output, &output_desc); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + memset(&mode, 0, sizeof(mode)); + mode.dmSize = sizeof(mode); + if (!EnumDisplaySettingsW(output_desc.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) + mode.dmDisplayFrequency = 0; + + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&start); + for (i = 0; i < 4; ++i) + { + hr = IDXGIOutput_WaitForVBlank(output); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + } + QueryPerformanceCounter(&end); + elapsed_ms = (end.QuadPart - start.QuadPart) * 1000.0 / frequency.QuadPart; + + /* Four waits span at least three full refresh periods; allow generous + * slack for scheduling, and an upper bound to catch a hang. */ + ok(elapsed_ms < 1000.0, "WaitForVBlank took %.2f ms.\n", elapsed_ms); + if (mode.dmDisplayFrequency > 1) + ok(elapsed_ms >= 3 * 1000.0 / mode.dmDisplayFrequency * 0.5, + "Four waits at %lu Hz took only %.2f ms.\n", mode.dmDisplayFrequency, elapsed_ms); + + IDXGIOutput_Release(output); + IDXGIAdapter_Release(adapter); + IDXGIFactory_Release(factory); +} + static void test_output_desc(void) { IDXGIAdapter *adapter, *adapter2; @@ -9085,6 +9143,7 @@ START_TEST(dxgi) queue_test(test_private_data); queue_test(test_maximum_frame_latency); queue_test(test_output_desc); + queue_test(test_output_wait_for_vblank); queue_test(test_object_wrapping); queue_test(test_factory_check_feature_support); queue_test(test_video_memory_budget_notification); -- 2.56.0