static void swapBuffersWGL(_GLFWwindow* window) { // HACK: Use DwmFlush when desktop composition is enabled if (_glfwIsCompositionEnabledWin32() && !window->monitor) { int count = abs(window->context.wgl.interval); while (count--) DwmFlush(); } SwapBuffers(window->context.wgl.dc); }
static void swapBuffersWGL(_GLFWwindow* window) { if (!window->monitor) { if (IsWindowsVistaOrGreater()) { BOOL enabled; // HACK: Use DwmFlush when desktop composition is enabled if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled) { int count = abs(window->context.wgl.interval); while (count--) DwmFlush(); } } } SwapBuffers(window->context.wgl.dc); }
static bool compositor_active(MPGLContext *ctx) { // For Windows 7. BOOL enabled = 0; if (FAILED(DwmIsCompositionEnabled(&enabled)) || !enabled) return false; // This works at least on Windows 8.1: it returns an error in fullscreen, // which is also when we get consistent timings without DwmFlush. Might // be cargo-cult. DWM_TIMING_INFO info = { .cbSize = sizeof(DWM_TIMING_INFO) }; if (FAILED(DwmGetCompositionTimingInfo(0, &info))) return false; // Test if a program is running in exclusive fullscreen mode. If so, it's // probably this one, so it's not getting redirected by the compositor. if (mp_w32_is_in_exclusive_mode()) return false; return true; } static void w32_swap_buffers(MPGLContext *ctx) { struct w32_context *w32_ctx = ctx->priv; SwapBuffers(w32_ctx->hdc); // default if we don't DwmFLush int new_swapinterval = w32_ctx->opt_swapinterval; if (ctx->dwm_flush_opt >= 0) { if ((ctx->dwm_flush_opt == 1 && !ctx->vo->opts->fullscreen) || (ctx->dwm_flush_opt == 2) || (ctx->dwm_flush_opt == 0 && compositor_active(ctx))) { if (DwmFlush() == S_OK) new_swapinterval = 0; } } if (new_swapinterval != w32_ctx->current_swapinterval && w32_ctx->real_wglSwapInterval) { w32_ctx->real_wglSwapInterval(new_swapinterval); MP_VERBOSE(ctx->vo, "set SwapInterval(%d)\n", new_swapinterval); } w32_ctx->current_swapinterval = new_swapinterval; } static int w32_control(MPGLContext *ctx, int *events, int request, void *arg) { return vo_w32_control(ctx->vo, events, request, arg); } const struct mpgl_driver mpgl_driver_w32 = { .name = "w32", .priv_size = sizeof(struct w32_context), .init = w32_init, .reconfig = w32_reconfig, .swap_buffers = w32_swap_buffers, .control = w32_control, .uninit = w32_uninit, };
static LRESULT CALLBACK ShieldWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) { static int left, top, width, height; static HDC whiteCtx, blackCtx; static HBITMAP white, black; static uint8_t* buffer; switch (message) { case WM_CREATE: { CREATESTRUCT* cs = (CREATESTRUCT*)lParam; HDC desktop = GetDC(NULL); buffer = cs->lpCreateParams; left = cs->x; top = cs->y; width = cs->cx; height = cs->cy; /* These bitmaps will be used to store the light and dark screenshots */ whiteCtx = CreateCompatibleDC(desktop); white = CreateCompatibleBitmap(desktop, width, height); blackCtx = CreateCompatibleDC(desktop); black = CreateCompatibleBitmap(desktop, width, height); ReleaseDC(NULL, desktop); if (!whiteCtx || !white || !blackCtx || !black) return -1; SetTimer(window, 101, 0, NULL); return 0; } case WM_TIMER: { HDC desktop = GetDC(NULL); KillTimer(window, 101); DwmFlush(); /* The shield window is created with a white background, so take the first screenshot */ SelectObject(whiteCtx, white); BitBlt(whiteCtx, 0, 0, width, height, desktop, left, top, SRCCOPY | CAPTUREBLT); /* Change the window background to black and update it */ SetClassLongPtr(window, GCLP_HBRBACKGROUND, (long)GetStockObject(BLACK_BRUSH)); InvalidateRect(window, NULL, TRUE); UpdateWindow(window); DwmFlush(); /* Now take the second screenshot */ SelectObject(blackCtx, black); BitBlt(blackCtx, 0, 0, width, height, desktop, left, top, SRCCOPY | CAPTUREBLT); ReleaseDC(NULL, desktop); DestroyWindow(window); return 0; } case WM_DESTROY: { size_t pitch = width * 4, size = height * pitch; uint8_t* blackBuf = xmalloc(size); GetBits(buffer, width, height, white, whiteCtx); DeleteDC(whiteCtx); DeleteObject(white); GetBits(blackBuf, width, height, black, blackCtx); DeleteDC(blackCtx); DeleteObject(black); /* Get an image with an alpha channel from both bitmaps */ ProcessAlpha(buffer, blackBuf, size); free(blackBuf); /* Exit this modal message loop so the program can continue */ PostQuitMessage(0); return 0; } } return DefWindowProc(window, message, wParam, lParam); }
/** Blocks the CPU to synchronize with vblank by communicating with DWM. */ void FD3D11Viewport::PresentWithVsyncDWM() { #if D3D11_WITH_DWMAPI LARGE_INTEGER Cycles; DWM_TIMING_INFO TimingInfo; // Find out how long since we last flipped and query DWM for timing information. QueryPerformanceCounter(&Cycles); FMemory::MemZero(TimingInfo); TimingInfo.cbSize = sizeof(DWM_TIMING_INFO); DwmGetCompositionTimingInfo(WindowHandle, &TimingInfo); uint64 QpcAtFlip = Cycles.QuadPart; uint64 CyclesSinceLastFlip = Cycles.QuadPart - LastFlipTime; float CPUTime = FPlatformTime::ToMilliseconds(CyclesSinceLastFlip); float GPUTime = FPlatformTime::ToMilliseconds(TimingInfo.qpcFrameComplete - LastCompleteTime); float DisplayRefreshPeriod = FPlatformTime::ToMilliseconds(TimingInfo.qpcRefreshPeriod); // Find the smallest multiple of the refresh rate that is >= 33ms, our target frame rate. float RefreshPeriod = DisplayRefreshPeriod; if (RHIConsoleVariables::bForceThirtyHz && RefreshPeriod > 1.0f) { while (RefreshPeriod - (1000.0f / 30.0f) < -1.0f) { RefreshPeriod *= 2.0f; } } // If the last frame hasn't completed yet, we don't know how long the GPU took. bool bValidGPUTime = (TimingInfo.cFrameComplete > LastFrameComplete); if (bValidGPUTime) { GPUTime /= (float)(TimingInfo.cFrameComplete - LastFrameComplete); } // Update the sync counter depending on how much time it took to complete the previous frame. float FrameTime = FMath::Max<float>(CPUTime, GPUTime); if (FrameTime >= RHIConsoleVariables::SyncRefreshThreshold * RefreshPeriod) { SyncCounter--; } else if (bValidGPUTime) { SyncCounter++; } SyncCounter = FMath::Clamp<int32>(SyncCounter, 0, RHIConsoleVariables::MaxSyncCounter); // If frames are being completed quickly enough, block for vsync. bool bSync = (SyncCounter >= RHIConsoleVariables::SyncThreshold); if (bSync) { // This flushes the previous present call and blocks until it is made available to DWM. D3DRHI->GetDeviceContext()->Flush(); DwmFlush(); // We sleep a percentage of the remaining time. The trick is to get the // present call in after the vblank we just synced for but with time to // spare for the next vblank. float MinFrameTime = RefreshPeriod * RHIConsoleVariables::RefreshPercentageBeforePresent; float TimeToSleep; do { QueryPerformanceCounter(&Cycles); float TimeSinceFlip = FPlatformTime::ToMilliseconds(Cycles.QuadPart - LastFlipTime); TimeToSleep = (MinFrameTime - TimeSinceFlip); if (TimeToSleep > 0.0f) { FPlatformProcess::Sleep(TimeToSleep * 0.001f); } } while (TimeToSleep > 0.0f); } // Present. PresentChecked(/*SyncInterval=*/ 0); // If we are forcing <= 30Hz, block the CPU an additional amount of time if needed. // This second block is only needed when RefreshPercentageBeforePresent < 1.0. if (bSync) { LARGE_INTEGER LocalCycles; float TimeToSleep; bool bSaveCycles = false; do { QueryPerformanceCounter(&LocalCycles); float TimeSinceFlip = FPlatformTime::ToMilliseconds(LocalCycles.QuadPart - LastFlipTime); TimeToSleep = (RefreshPeriod - TimeSinceFlip); if (TimeToSleep > 0.0f) { bSaveCycles = true; FPlatformProcess::Sleep(TimeToSleep * 0.001f); } } while (TimeToSleep > 0.0f); if (bSaveCycles) { Cycles = LocalCycles; } } // If we are dropping vsync reset the counter. This provides a debounce time // before which we try to vsync again. if (!bSync && bSyncedLastFrame) { SyncCounter = 0; } if (bSync != bSyncedLastFrame || UE_LOG_ACTIVE(LogRHI,VeryVerbose)) { UE_LOG(LogRHI,Verbose,TEXT("BlockForVsync[%d]: CPUTime:%.2fms GPUTime[%d]:%.2fms Blocked:%.2fms Pending/Complete:%d/%d"), bSync, CPUTime, bValidGPUTime, GPUTime, FPlatformTime::ToMilliseconds(Cycles.QuadPart - QpcAtFlip), TimingInfo.cFramePending, TimingInfo.cFrameComplete); } // Remember if we synced, when the frame completed, etc. bSyncedLastFrame = bSync; LastFlipTime = Cycles.QuadPart; LastFrameComplete = TimingInfo.cFrameComplete; LastCompleteTime = TimingInfo.qpcFrameComplete; #endif //D3D11_WITH_DWMAPI }
void VSyncWin::VSyncLoop() { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); DWM_TIMING_INFO vblankTime; // Make sure to init the cbSize, otherwise GetCompositionTiming will fail vblankTime.cbSize = sizeof(DWM_TIMING_INFO); LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); TimeStamp vsync = Now(); // On Windows 10, DwmGetCompositionInfo returns the upcoming vsync. // See GetAdjustedVsyncTimestamp. // On start, set mPrevVsync to the "next" vsync // So we'll use this timestamp on the 2nd loop iteration. mPrevVsync = vsync + mSoftwareVsyncRate; for (;;) { #if !USING_DWM_FLUSH TimeStamp here = Now(); ::QueryPerformanceCounter(&gSS); //Sleep(1); //Sleep(200); m_pOutput->WaitForVBlank(); //Sleep(1); m_pSwapChain->Present(0, 0); //DwmFlush(); //Gdi: NtGdiDdDDIWaitForVerticalBlankEvent() //TimerBlit(); //gS = gSS; //::PostMessageA(gHwnd, WM_BLIT_REQUEST, 0, 0); //TimerBlit(); FILE *f; fopen_s(&f, "C:\\VSyncThread", "rb"); //char buf[200]; LARGE_INTEGER fi; char buf[200]; ::QueryPerformanceFrequency(&fi); sprintf_s(buf, "\n+++++ %d", int((Now() - here) * 1000000 / fi.QuadPart)); OutputDebugStringA(buf); #else ::QueryPerformanceCounter(&gS); gTS = vsync; /*mWork = CreateThreadpoolWork(BlittingThread, (void*)&gTS, &mCallBackEnviron); SubmitThreadpoolWork(mWork);*/ //mVSyncTimeStamp.store(vsync); char buf[1000]; LARGE_INTEGER fi; //::QueryPerformanceFrequency(&fi); //sprintf_s(buf, "\n+++++%d", int((vsync - here) * 1000000 / fi.QuadPart)); //OutputDebugStringA(buf); // Use a combination of DwmFlush + DwmGetCompositionTimingInfoPtr // Using WaitForVBlank, the whole system dies :/ //m_pSwapChain->Present(0, 0); //::PostMessageA(gHwnd, WM_BLIT_REQUEST, 0, 0); //m_pSwapChain->Present(1, 0); //TimerBlit(); HRESULT hr; hr = DwmFlush();//WinUtils::dwmFlushProcPtr(); if (!SUCCEEDED(hr)) { // // We don't actually know how long we had to wait on DWMFlush // // Instead of trying to calculate how long DwmFlush actually took // // Fallback to software vsync. // //-->ScheduleSoftwareVsync(Now()); return; } //dwmflush does not exactly return at next vblank //TimerBlit(); hr = DwmGetCompositionTimingInfo(0, &vblankTime); /*vsync = SUCCEEDED(hr) ? GetAdjustedVsyncTimeStamp(frequency, vblankTime.qpcVBlank) : Now();*/ vsync = vblankTime.qpcVBlank;// +vblankTime.qpcRefreshPeriod; mVSyncTimeStamp.store(vsync); mBlit = true; ////char buf[200]; //LARGE_INTEGER l; // //QueryPerformanceFrequency(&l); ////if (((vsync - Now()) * 1000000 / l.QuadPart) > 10000) // //continue; ///*LARGE_INTEGER le, lf; //QueryPerformanceCounter(&le); //QueryPerformanceFrequency(&lf);*/ //sprintf_s(buf, "\nvblank Time Now = %u", (vsync - Now()) * 1000000 / l.QuadPart); //OutputDebugStringA(buf); //sprintf_s(buf, "\n****** ---- %llu --", vsync); //OutputDebugStringA(buf); /*sprintf_s(buf, "\nv Timed %d ", ((le.QuadPart - gS.QuadPart) * 1000000 / lf.QuadPart)); OutputDebugStringA(buf);*/ #endif } // end for }