You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WIN32游戏循环运行缓慢,无法达到30/60Hz,如何解决?

问题分析与修复方案

核心问题点

  1. 定时器时间计算逻辑错误
    你设置的loop_time.QuadPart = -166666LL*2是把预期循环时长(33.333ms)以相对负时间表示,但后续计算rest_time的公式完全错误。正确的剩余等待时间应该是预期总时长减去已消耗时间,再转换成可等待定时器要求的负相对时间。

  2. 超时后无时间补偿
    当rest_time >=0(已超时)时直接continue,没有记录超时的时间差,导致后续循环无法补上丢失的帧,帧率会持续低于目标值。

  3. 硬编码性能计数器频率
    强制要求frequency.QuadPart == 10000000LL会让程序在大多数现代系统上直接退出——现在很多CPU的性能计数器频率远高于10MHz,这种校验完全没必要。

  4. 消息处理不彻底
    单次PeekMessage可能导致消息队列堆积,虽然你说换成while也没解决,但这是游戏循环的标准做法,配合定时器等待才是正确方向。

修复步骤

  • 移除性能计数器频率的硬编码校验,改用实际计算的频率换算时间。
  • 重新计算定时器等待时间:先算出已消耗的时间,用目标循环时长减去已消耗时间,若结果为正,则转换成负的相对时间给定时器;若为负,记录超时时间并在下一循环补偿。
  • 超时后不要直接跳过等待,而是累积超时时间,让后续循环尽量追赶。
  • 用while处理所有待处理消息,避免消息阻塞。

修改后的代码

#include <Windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    static const wchar_t class_name[] = L"Test";
    WNDCLASS wc{ };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = class_name;
    if (!RegisterClassW(&wc))
        return 0;

    RECT client_rect = {0, 0, 800, 600};
    if (!AdjustWindowRect(&client_rect, WS_OVERLAPPEDWINDOW, FALSE))
        return 0;

    HWND hwnd = CreateWindowEx(
        WS_EX_OVERLAPPEDWINDOW, class_name, L"Test",
        WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED,
        100, 100, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top,
        0, 0, hInstance, 0
    );
    if (hwnd == 0)
        return 0;

    ShowWindow(hwnd, SW_SHOWNORMAL);

    LARGE_INTEGER frequency;
    if (!QueryPerformanceFrequency(&frequency))
        return 0;
    // 用实际频率动态计算目标帧刻度数,适配所有系统
    const double target_fps = 30.0;
    const LONGLONG target_frame_ticks = static_cast<LONGLONG>(frequency.QuadPart / target_fps);

    HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
    if (hTimer == 0)
        return 0;

    LARGE_INTEGER start_time, end_time, wait_time;
    LONGLONG accumulated_overtime = 0;
    bool timeExceeded = false;

    MSG msg = { };
    bool running = true;

    while (running)
    {
        QueryPerformanceCounter(&start_time);

        // 处理所有待处理消息,避免队列阻塞
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                running = false;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // --- 这里放置游戏逻辑、渲染等代码 ---

        QueryPerformanceCounter(&end_time);
        LONGLONG elapsed_ticks = end_time.QuadPart - start_time.QuadPart;
        // 计算需要等待的时间:目标时长 - 已耗时 - 之前累积的超时
        LONGLONG needed_wait_ticks = target_frame_ticks - elapsed_ticks - accumulated_overtime;

        if (needed_wait_ticks > 0)
        {
            timeExceeded = false;
            // 可等待定时器需要负的相对时间(单位100ns)
            wait_time.QuadPart = -needed_wait_ticks;
            SetWaitableTimer(hTimer, &wait_time, 0, 0, 0, 0);
            WaitForSingleObject(hTimer, INFINITE);
            accumulated_overtime = 0;
        }
        else
        {
            timeExceeded = true;
            // 累积超时时间,后续循环尝试追赶
            accumulated_overtime = -needed_wait_ticks;
        }
    }

    CloseHandle(hTimer);
    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

关键改动说明

  • 用frequency.QuadPart / target_fps动态计算目标帧的刻度数,适配所有系统的性能计数器频率。
  • 增加accumulated_overtime变量累积超时时间,避免丢帧累积导致帧率持续走低。
  • 正确转换等待时间为可等待定时器要求的负相对时间(负数表示相对当前时间的延迟,单位100ns)。
  • 用while处理所有消息,确保消息队列不会阻塞循环。

内容的提问来源于stack exchange,提问作者WhyNot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:05:12