Created
December 6, 2021 10:56
-
-
Save kumarchandresh/43c40bb24db9fe45053f26d8613aca8c to your computer and use it in GitHub Desktop.
How to limit FPS using C++ on Win32.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <tchar.h> | |
| #include <Windows.h> | |
| #define TARGET_FPS 60 | |
| INT64 QPCFrequency; | |
| LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); | |
| INT64 __forceinline ElapsedMicroseconds(INT64 startCount, INT64 endCount) | |
| { | |
| INT64 elapsedMicroseconds = endCount - startCount; | |
| elapsedMicroseconds *= 1000000; | |
| elapsedMicroseconds /= QPCFrequency; | |
| return elapsedMicroseconds; | |
| } | |
| int WINAPI wWinMain( | |
| _In_ HINSTANCE hInstance, | |
| _In_opt_ HINSTANCE hPrevInstance, | |
| _In_ LPWSTR lpCmdLine, | |
| _In_ int nCmdShow) | |
| { | |
| WNDCLASSEX wcx = { 0 }; | |
| MSG msg = { 0 }; | |
| HWND hwnd; | |
| INT32 frameCount = 0; | |
| INT64 frameStart = 0, frameEnd = 0; | |
| INT64 averageFPS = 0, ticksAccumulator = 0; | |
| INT64 elapsedTime, overSleepDuration = 0; | |
| const INT64 TARGET_FRAME_TIME = (1000000 / TARGET_FPS) + 1; | |
| TCHAR outstr[1024]; | |
| UNREFERENCED_PARAMETER(hPrevInstance); | |
| UNREFERENCED_PARAMETER(lpCmdLine); | |
| QueryPerformanceFrequency((LARGE_INTEGER*)&QPCFrequency); | |
| wcx.cbSize = sizeof(WNDCLASSEX); | |
| wcx.style = CS_HREDRAW | CS_VREDRAW; | |
| wcx.lpfnWndProc = WindowProc; | |
| wcx.cbClsExtra = 0; | |
| wcx.cbWndExtra = 0; | |
| wcx.hInstance = hInstance; | |
| wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION); | |
| wcx.hCursor = LoadCursor(NULL, IDC_ARROW); | |
| wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); | |
| wcx.lpszMenuName = NULL; | |
| wcx.lpszClassName = _T("MainWindow"); | |
| wcx.hIconSm = NULL; | |
| if (!RegisterClassEx(&wcx)) | |
| { | |
| MessageBox(NULL, _T("Failed RegisterClassEx"), _T("ERROR!"), MB_ICONERROR | MB_OK); | |
| return GetLastError(); | |
| } | |
| hwnd = CreateWindowEx(0, | |
| _T("MainWindow"), | |
| _T("Window Title"), | |
| WS_OVERLAPPEDWINDOW, | |
| CW_USEDEFAULT, CW_USEDEFAULT, | |
| CW_USEDEFAULT, CW_USEDEFAULT, | |
| (HWND)NULL, | |
| (HMENU)NULL, | |
| hInstance, | |
| (LPVOID)NULL); | |
| if (!hwnd) | |
| { | |
| MessageBox(NULL, _T("Failed CreateWindowEx"), _T("ERROR!"), MB_ICONERROR | MB_OK); | |
| return GetLastError(); | |
| } | |
| ShowWindow(hwnd, nCmdShow); | |
| UpdateWindow(hwnd); | |
| QueryPerformanceCounter((LARGE_INTEGER*)&frameStart); | |
| // Game Loop | |
| while (true) | |
| { | |
| while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) | |
| { | |
| TranslateMessage(&msg); | |
| DispatchMessage(&msg); | |
| } | |
| if (msg.message == WM_QUIT) | |
| return(int)msg.wParam; | |
| // Render | |
| HDC hdc = GetDC(hwnd); | |
| SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FIXED_FONT)); | |
| _sntprintf_s(outstr, _countof(outstr), _TRUNCATE, _T("FPS %lld"), averageFPS); | |
| TextOut(hdc, 0, 0, outstr, (int)_tcslen(outstr)); | |
| ReleaseDC(hwnd, hdc); | |
| // Limit FPS | |
| /* | |
| * Target Frame Time | |
| * +-------------------+ | |
| * | |
| * |--------------+----|---+---------------|-------------------| | |
| * | |
| * +--------------+ | |
| * Frame Time +--------+ | |
| * Sleep | |
| * +---+ Oversleep | |
| * | |
| * Keep accounting for oversleeping period until there is enough to skip a Sleep() call. | |
| */ | |
| QueryPerformanceCounter((LARGE_INTEGER*)&frameEnd); | |
| elapsedTime = ElapsedMicroseconds(frameStart, frameEnd); | |
| while (elapsedTime < TARGET_FRAME_TIME) | |
| { | |
| if ((elapsedTime + overSleepDuration) >= TARGET_FRAME_TIME) | |
| { | |
| overSleepDuration -= TARGET_FRAME_TIME - elapsedTime; | |
| break; | |
| } | |
| Sleep(1); | |
| QueryPerformanceCounter((LARGE_INTEGER*)&frameEnd); | |
| elapsedTime = ElapsedMicroseconds(frameStart, frameEnd); | |
| if (elapsedTime > TARGET_FRAME_TIME) | |
| overSleepDuration += elapsedTime - TARGET_FRAME_TIME; | |
| } | |
| QueryPerformanceCounter((LARGE_INTEGER*)&frameEnd); | |
| ticksAccumulator += frameEnd - frameStart; | |
| frameCount += 1; | |
| if ((frameCount % TARGET_FPS) == 0) | |
| { | |
| averageFPS = ((QPCFrequency * TARGET_FPS) + (ticksAccumulator - 1)) / ticksAccumulator; // round-off | |
| ticksAccumulator = 0; | |
| frameCount = 0; | |
| } | |
| frameStart = frameEnd; | |
| } | |
| return 0; | |
| } | |
| LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |
| { | |
| switch (message) | |
| { | |
| case WM_CLOSE: | |
| case WM_DESTROY: | |
| PostQuitMessage(0); | |
| break; | |
| default: | |
| return DefWindowProc(hwnd, message, wParam, lParam); | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment