首页 > 解决方案 > 多窗口绘画

问题描述

如何避免我的 Windows 帧速率卡在瓶颈窗口?

我的问题是当我将两个窗口一起显示时,我通过窗口获得 10 FPS。

我原以为快窗口会比慢窗口快,但是两个窗口在同时绘画时获得相同的较慢 FPS 速度。

我期望慢速窗口获得大约 5 FPS,快速窗口获得 30 FPS。

已尝试使用 timeSetEvent() 而不是 SetTimer(),当设置时间很短时,它只会绘制慢速窗口。

尝试使用 timeSetEvent(TIME_ONESHOT) 自定义绘制消息,这给了我与 WM_TIMER 相同的缓慢结果。

添加了关闭/开启按钮,以便在不更改代码的情况下轻松查看 FPS 正在做什么。

据我了解,快窗口正在等待慢窗口完成他的绘画。非常非常感谢您的帮助...

这里是完整的代码main.c

#include <stdio.h>
#include <string.h>
#include <windows.h>

#define TIMER_WND0      0
#define TIMER_WND1      1

#define OFF_ON_WND0     0
#define OFF_ON_WND1     1

LRESULT CALLBACK procWndMain(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK procWnd0(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK procWnd1(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hInst = NULL;
HWND hWndMain = NULL;
HWND hWnd0 = NULL;
HWND hWnd1 = NULL;

int offOnWnd0 = 1;
int offOnWnd1 = 1;

void uint2str(char *buf, unsigned int x) {
    sprintf(buf, "%u", x);
    }
double millitimeTick(void) {
    return GetTickCount()/1000.0;
    }
int randInt(int a, int b) {
    return rand()%(b-a) +a;
    }

void createWindowClass(HINSTANCE hInstance, WNDPROC wndProc, LPCSTR lpszClassName, int bg, unsigned int style) {
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = style;
    wc.lpfnWndProc = wndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = lpszClassName;
    if(bg) { wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); }
    else{ wc.hbrBackground = NULL; }
    if(!RegisterClassEx(&wc)) {
        MessageBox(NULL, lpszClassName, "Error RegisterClassEx", MB_OK);
        }
    }

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszArg, int nCmdShow) {
    (void)hPrevInst;
    (void)lpszArg;
    MSG message = {0};
    hInst = hInstance;

    createWindowClass(hInstance, procWndMain, "procWndMain", 1, CS_HREDRAW|CS_VREDRAW);
    hWndMain = CreateWindowEx(
        WS_EX_CONTROLPARENT,
        "procWndMain",
        "multiWndPaint",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        (int)CW_USEDEFAULT, (int)CW_USEDEFAULT,
        950, 370,
        HWND_DESKTOP,
        NULL,
        hInstance,
        NULL
        );
    ShowWindow(hWndMain, nCmdShow);
    UpdateWindow(hWndMain);

    while(GetMessage(&message, NULL, 0, 0)) {
        TranslateMessage(&message);
        DispatchMessage(&message);
        }

    UnregisterClass("procWndMain", hInst);
    return (int)message.wParam;
    }

LRESULT CALLBACK procWndMain(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch(msg) {
        case WM_CREATE: {
            createWindowClass(hInst, procWnd0, "procWnd0", 1, 0);
            hWnd0 = CreateWindowEx(
                0,
                "procWnd0",
                "",
                WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                20, 22,
                440, 300,
                hwnd,
                NULL,
                NULL,
                NULL
                );
            ShowWindow(hWnd0, SW_SHOW);

            createWindowClass(hInst, procWnd1, "procWnd1", 1, 0);
            hWnd1 = CreateWindowEx(
                0,
                "procWnd1",
                "",
                WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                480, 22,
                440, 300,
                hwnd,
                NULL,
                NULL,
                NULL
                );
            ShowWindow(hWnd1, SW_SHOW);

            HWND btnWnd0 = CreateWindowEx(0, "BUTTON", "Off/On Wnd0",
                WS_CHILD | WS_VISIBLE,
                20, 0, 104, 24,
                hwnd,
                (HMENU)OFF_ON_WND0,
                NULL, NULL
                );
            ShowWindow(btnWnd0, SW_SHOW);

            HWND btnWnd1 = CreateWindowEx(0, "BUTTON", "Off/On Wnd1",
                WS_CHILD | WS_VISIBLE,
                480, 0, 104, 24,
                hwnd,
                (HMENU)OFF_ON_WND1,
                NULL, NULL
                );
            ShowWindow(btnWnd1, SW_SHOW);
            break;
            }

        case WM_COMMAND: {
            if(LOWORD(wParam) == OFF_ON_WND0) {
                if(offOnWnd0) {
                    KillTimer(hWnd0, TIMER_WND0);
                    offOnWnd0 = 0;
                    }
                else{
                    SetTimer(hWnd0, TIMER_WND0, 1, NULL);
                    offOnWnd0 = 1;
                    }
                }

            else if(LOWORD(wParam) == OFF_ON_WND1) {
                if(offOnWnd1) {
                    KillTimer(hWnd1, TIMER_WND1);
                    offOnWnd1 = 0;
                    }
                else{
                    SetTimer(hWnd1, TIMER_WND1, 1, NULL);
                    offOnWnd1 = 1;
                    }
                }
            break;
            }

        case WM_DESTROY: {
            KillTimer(hWnd0, TIMER_WND0);
            KillTimer(hWnd1, TIMER_WND1);
            UnregisterClass("procWnd0", hInst);
            UnregisterClass("procWnd1", hInst);
            PostQuitMessage(0);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

void wndInfo(HDC *hdcMem, char *name, unsigned int frame, unsigned int frameRate, unsigned int frameMax) {
    char buf64[24];
    char textInfo[256];
    RECT rect = {0};

    strcpy(textInfo, name);
    strcat(textInfo, "\n");

    uint2str(buf64, frame);
    strcat(textInfo, "Frame : ");
    strcat(textInfo, buf64);

    uint2str(buf64, frameRate);
    strcat(textInfo, "\nFPS : ");
    strcat(textInfo, buf64);

    uint2str(buf64, frameMax);
    strcat(textInfo, "\nMax : ");
    strcat(textInfo, buf64);

    HBRUSH brush = (HBRUSH)GetStockObject(WHITE_BRUSH);
    rect.right = 100;
    rect.bottom = 70;
    FillRect(*hdcMem, &rect, brush);

    rect.left = 4;
    rect.top = 2;
    rect.right = 100;
    rect.bottom = 70;
    SetTextColor(*hdcMem, RGB(0,0,255));
    DrawText(*hdcMem, textInfo, -1, &rect, DT_NOCLIP|DT_NOPREFIX);

    DeleteObject(brush);
    }

LRESULT CALLBACK procWnd0(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static double timestamp = 0;
    static unsigned int frame = 0;
    static unsigned int frameRate = 0;
    static unsigned int frameMax = 0;

    switch(msg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rect = {0};
            GetClientRect(hwnd, &rect);

            HDC hdcMem = CreateCompatibleDC(hdc);
            HBITMAP bmpMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
            HBITMAP bmpMemOld = SelectObject(hdcMem, bmpMem);

            SetBkMode(hdcMem, TRANSPARENT);
            SetTextColor(hdcMem, RGB(255,255,255));

            HBRUSH brush = (HBRUSH)GetStockObject(GRAY_BRUSH);
            FillRect(hdcMem, &rect, brush);

            POINT point = {0};
            size_t i = 0;
            size_t len = 99999;
            while(i < len) {
                point.x = randInt(-rect.right*8, rect.right*8);
                point.y = randInt(-rect.bottom*8, rect.bottom*8);
                TextOut(hdcMem, point.x, point.y, "foobar", 6);
                i++;
                }

            frame++;
            double timestampCur = millitimeTick();
            if(timestampCur > timestamp+1.0) {
                timestamp = timestampCur;
                frameRate = frame;
                if(frame > frameMax) { frameMax = frame; }
                frame = 0;
                }

            wndInfo(&hdcMem, "Wnd0", frame, frameRate, frameMax);

            BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

            DeleteObject(brush);
            SelectObject(hdcMem, bmpMemOld);
            DeleteDC(hdcMem);
            DeleteObject(bmpMem);
            EndPaint(hwnd, &ps);
            break;
            }

        case WM_ERASEBKGND: {
            break;
            }

        case WM_TIMER: {
            if(wParam == TIMER_WND0) {
                InvalidateRect(hwnd, NULL, TRUE);
                }
            break;
            }

        case WM_CREATE: {
            SetTimer(hwnd, TIMER_WND0, 1, NULL);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

LRESULT CALLBACK procWnd1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    static double timestamp = 0;
    static unsigned int frame = 0;
    static unsigned int frameRate = 0;
    static unsigned int frameMax = 0;

    switch(msg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rect = {0};
            GetClientRect(hwnd, &rect);

            HDC hdcMem = CreateCompatibleDC(hdc);
            HBITMAP bmpMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
            HBITMAP bmpMemOld = SelectObject(hdcMem, bmpMem);

            SetBkMode(hdcMem, TRANSPARENT);
            SetTextColor(hdcMem, RGB(255,255,255));

            HBRUSH brush = (HBRUSH)GetStockObject(GRAY_BRUSH);
            FillRect(hdcMem, &rect, brush);

            POINT point = {0};
            size_t i = 0;
            size_t len = 999;
            while(i < len) {
                point.x = randInt(-rect.right*8, rect.right*8);
                point.y = randInt(-rect.bottom*8, rect.bottom*8);
                TextOut(hdcMem, point.x, point.y, "foobar", 6);
                i++;
                }

            frame++;
            double timestampCur = millitimeTick();
            if(timestampCur > timestamp+1.0) {
                timestamp = timestampCur;
                frameRate = frame;
                if(frame > frameMax) { frameMax = frame; }
                frame = 0;
                }

            wndInfo(&hdcMem, "Wnd1", frame, frameRate, frameMax);

            BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

            DeleteObject(brush);
            SelectObject(hdcMem, bmpMemOld);
            DeleteDC(hdcMem);
            DeleteObject(bmpMem);
            EndPaint(hwnd, &ps);
            break;
            }

        case WM_ERASEBKGND: {
            break;
            }

        case WM_TIMER: {
            if(wParam == TIMER_WND1) {
                InvalidateRect(hwnd, NULL, TRUE);
                }
            break;
            }

        case WM_CREATE: {
            SetTimer(hwnd, TIMER_WND1, 1, NULL);
            break;
            }

        default: {
            return DefWindowProc(hwnd, msg, wParam, lParam);
            }
        }
    return 0;
    }

标签: cwinapi

解决方案


绘制60 FPS 窗口大约需要 16 毫秒或更短的时间。我说“或更少”是因为,尽管您请求了 1 毫秒的计时器间隔,但您可能会得到大约 16 毫秒的时间,因为这是这些计时器的默认分辨率。因此,您的快速窗口受限于计时器分辨率而不是其绘制速度。

您的 10 FPS 窗口绘制大约需要 100 毫秒,并且“下一个”间隔已经过去,因此计时器应该立即触发。下一帧大约需要 100 毫秒的事实表明实际绘制时间非常接近 100 毫秒。

当您同时显示两个窗口时,它们不会并行绘制。WM_TIMER 消息在您的消息循环调用 GetMessage 和 DispatchMessage 时发送。如果您的代码在窗口二的计时器到期时正忙于绘制窗口一,那么窗口二的 WM_TIMER 消息将被延迟,直到窗口一完成并从其窗口过程返回到消息循环。

所以其中一个被画了,然后另一个。您再次受到两个窗口的总时间的限制。快窗口不能比慢窗口运行得快。

(还有其他并发症,但这些是您实验中的主要因素。)

这里没有一个很好的解决方案。在同一进程中从多个线程使用 GDI 是出了名的困难。如果您可以将一个窗口放在一个进程中,将另一个窗口放在另一个进程中,那么您可以直观地看到它们以最高速度并排运行。


推荐阅读